summaryrefslogtreecommitdiffstats
path: root/src/lib
diff options
context:
space:
mode:
authorNarayan Desai <desai@mcs.anl.gov>2007-12-29 04:57:53 +0000
committerNarayan Desai <desai@mcs.anl.gov>2007-12-29 04:57:53 +0000
commit3610288cbcbf4d1adedefa03166cd77ee15aad96 (patch)
tree1588fb78ae1c6e51e4e4ea2dab46a006a3ece0d3 /src/lib
parent6bf7875bc299a1f81061782c8646c90972e06e5a (diff)
downloadbcfg2-3610288cbcbf4d1adedefa03166cd77ee15aad96.tar.gz
bcfg2-3610288cbcbf4d1adedefa03166cd77ee15aad96.tar.bz2
bcfg2-3610288cbcbf4d1adedefa03166cd77ee15aad96.zip
Refactor of bcfg2-admin (all modes moved to discrete modules in Bcfg2.Server.Admin
git-svn-id: https://svn.mcs.anl.gov/repos/bcfg/trunk/bcfg2@4125 ce84e21b-d406-0410-9b95-82705330c041
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/Server/Admin/Client.py46
-rw-r--r--src/lib/Server/Admin/Client.pycbin0 -> 2173 bytes
-rw-r--r--src/lib/Server/Admin/Compare.py131
-rw-r--r--src/lib/Server/Admin/Compare.pycbin0 -> 4806 bytes
-rw-r--r--src/lib/Server/Admin/Fingerprint.py19
-rw-r--r--src/lib/Server/Admin/Fingerprint.pycbin0 -> 1298 bytes
-rw-r--r--src/lib/Server/Admin/Init.py119
-rw-r--r--src/lib/Server/Admin/Init.pycbin0 -> 4346 bytes
-rw-r--r--src/lib/Server/Admin/Minestruct.py25
-rw-r--r--src/lib/Server/Admin/Minestruct.pycbin0 -> 1686 bytes
-rw-r--r--src/lib/Server/Admin/Pull.py99
-rw-r--r--src/lib/Server/Admin/Pull.pycbin0 -> 4122 bytes
-rw-r--r--src/lib/Server/Admin/Tidy.py52
-rw-r--r--src/lib/Server/Admin/Tidy.pycbin0 -> 2067 bytes
-rw-r--r--src/lib/Server/Admin/Viz.py143
-rw-r--r--src/lib/Server/Admin/Viz.pycbin0 -> 5610 bytes
-rw-r--r--src/lib/Server/Admin/__init__.py45
-rw-r--r--src/lib/Server/Admin/__init__.pycbin0 -> 2485 bytes
-rw-r--r--src/lib/Server/__init__.py3
19 files changed, 681 insertions, 1 deletions
diff --git a/src/lib/Server/Admin/Client.py b/src/lib/Server/Admin/Client.py
new file mode 100644
index 000000000..677e38c2c
--- /dev/null
+++ b/src/lib/Server/Admin/Client.py
@@ -0,0 +1,46 @@
+import lxml.etree
+
+import Bcfg2.Server.Admin
+
+class Client(Bcfg2.Server.Admin.Mode):
+ __shorthelp__ = 'bcfg2-admin client add <client> attr1=val1 attr2=val2\nbcfg2-admin client del <client>'
+ __longhelp__ = __shorthelp__ + '\n\tCreate or delete client entries'
+ def __init__(self, configfile):
+ Bcfg2.Server.Admin.Mode.__init__(self, configfile)
+ self.tree = lxml.etree.parse(self.get_repo_path() + \
+ '/Metadata/clients.xml')
+ self.root = self.tree.getroot()
+
+ def __call__(self, args):
+ Bcfg2.Server.Admin.Mode.__call__(self, args)
+ repopath = self.get_repo_path()
+ if args[0] == 'add':
+ attr_d = {}
+ for i in args[1:]:
+ attr, val = i.split('=', 1)
+ if attr not in ['profile', 'uuid', 'password', 'address',
+ 'secure', 'location']:
+ print "Attribute %s unknown" % attr
+ raise SystemExit(1)
+ attr_d[attr] = val
+ self.AddClient(args[1], attr_d)
+ elif args[0] in ['delete', 'remove', 'del', 'rm']:
+ self.DelClient(args[1])
+ else:
+ print "No command specified"
+ raise SystemExit(1)
+ self.tree.write(repopath + '/Metadata/clients.xml')
+
+ def AddClient(self, client, attrs):
+ '''add a new client'''
+ # FIXME add a dup client check
+ element = lxml.etree.Element("Client", name=client)
+ for key, val in attrs.iteritems():
+ element.set(key, val)
+ self.root.append(element)
+
+ def DelClient(self, client):
+ '''delete an existing client'''
+ # FIXME DelClient not implemented
+ pass
+
diff --git a/src/lib/Server/Admin/Client.pyc b/src/lib/Server/Admin/Client.pyc
new file mode 100644
index 000000000..262b04f4b
--- /dev/null
+++ b/src/lib/Server/Admin/Client.pyc
Binary files differ
diff --git a/src/lib/Server/Admin/Compare.py b/src/lib/Server/Admin/Compare.py
new file mode 100644
index 000000000..962e54606
--- /dev/null
+++ b/src/lib/Server/Admin/Compare.py
@@ -0,0 +1,131 @@
+
+import lxml.etree, os
+import Bcfg2.Server.Admin
+
+class Compare(Bcfg2.Server.Admin.Mode):
+ __shorthelp__ = "bcfg2-admin compare <file1> <file2>\nbcfg2-admin compare -r <dir1> <dir2>"
+ __longhelp__ = __shorthelp__ + '''\n\tCompare mode determines differences between directories of client specification instances'''
+
+ def __init__(self, configfile):
+ Bcfg2.Server.Admin.Mode.__init__(self, configfile)
+ self.important = {'Package':['name', 'version'],
+ 'Service':['name', 'status'],
+ 'Directory':['name', 'owner', 'group', 'perms'],
+ 'SymLink':['name', 'to'],
+ 'ConfigFile':['name', 'owner', 'group', 'perms'],
+ 'Permissions':['name', 'perms'],
+ 'PostInstall':['name']}
+
+ def compareStructures(self, new, old):
+ for child in new.getchildren():
+ equiv = old.xpath('%s[@name="%s"]' % (child.tag, child.get('name')))
+ if child.tag in self.important:
+ print "tag type %s not handled" % (child.tag)
+ continue
+ if len(equiv) == 0:
+ print "didn't find matching %s %s" % (child.tag, child.get('name'))
+ continue
+ elif len(equiv) >= 1:
+ if child.tag == 'ConfigFile':
+ if child.text != equiv[0].text:
+ print " %s %s contents differ" \
+ % (child.tag, child.get('name'))
+ continue
+ noattrmatch = [field for field in self.important[child.tag] if \
+ child.get(field) != equiv[0].get(field)]
+ if not noattrmatch:
+ new.remove(child)
+ old.remove(equiv[0])
+ else:
+ print " %s %s attributes %s do not match" % \
+ (child.tag, child.get('name'), noattrmatch)
+ if len(old.getchildren()) == 0 and len(new.getchildren()) == 0:
+ return True
+ if new.tag == 'Independant':
+ name = 'Base'
+ else:
+ name = new.get('name')
+ both = []
+ oldl = ["%s %s" % (entry.tag, entry.get('name')) for entry in old]
+ newl = ["%s %s" % (entry.tag, entry.get('name')) for entry in new]
+ for entry in newl:
+ if entry in oldl:
+ both.append(entry)
+ newl.remove(entry)
+ oldl.remove(entry)
+ for entry in both:
+ print " %s differs (in bundle %s)" % (entry, name)
+ for entry in oldl:
+ print " %s only in old configuration (in bundle %s)" % (entry, name)
+ for entry in newl:
+ print " %s only in new configuration (in bundle %s)" % (entry, name)
+ return False
+
+ def compareSpecifications(self, path1, path2):
+ try:
+ new = lxml.etree.parse(path1).getroot()
+ except IOError:
+ print "Failed to read %s" % (path1)
+ raise SystemExit(1)
+
+ try:
+ old = lxml.etree.parse(path2).getroot()
+ except IOError:
+ print "Failed to read %s" % (path2)
+ raise SystemExit(1)
+
+ for src in [new, old]:
+ for bundle in src.findall('./Bundle'):
+ if bundle.get('name')[-4:] == '.xml':
+ bundle.set('name', bundle.get('name')[:-4])
+
+ rcs = []
+ for bundle in new.findall('./Bundle'):
+ equiv = old.xpath('Bundle[@name="%s"]' % (bundle.get('name')))
+ if len(equiv) == 0:
+ print "couldnt find matching bundle for %s" % bundle.get('name')
+ continue
+ if len(equiv) == 1:
+ if self.compareStructures(bundle, equiv[0]):
+ new.remove(bundle)
+ old.remove(equiv[0])
+ rcs.append(True)
+ else:
+ rcs.append(False)
+ else:
+ print "Unmatched bundle %s" % (bundle.get('name'))
+ rcs.append(False)
+ i1 = new.find('./Independant')
+ i2 = old.find('./Independant')
+ if self.compareStructures(i1, i2):
+ new.remove(i1)
+ old.remove(i2)
+ else:
+ rcs.append(False)
+ return False not in rcs
+
+ def __call__(self, args):
+ Bcfg2.Server.Admin.Mode.__call__(self, args)
+ if '-r' in args:
+ args = list(args)
+ args.remove('-r')
+ (oldd, newd) = args
+ (old, new) = [os.listdir(spot) for spot in args]
+ for item in old:
+ print "Entry:", item
+ state = self.__call__([oldd + '/' + item, newd + '/' + item])
+ new.remove(item)
+ if state:
+ print "Entry:", item, "good"
+ else:
+ print "Entry:", item, "bad"
+ if new:
+ print "new has extra entries", new
+ return
+ try:
+ (old, new) = args
+ except IndexError:
+ print self.__call__.__doc__
+ raise SystemExit
+
+
diff --git a/src/lib/Server/Admin/Compare.pyc b/src/lib/Server/Admin/Compare.pyc
new file mode 100644
index 000000000..e3c38f3cb
--- /dev/null
+++ b/src/lib/Server/Admin/Compare.pyc
Binary files differ
diff --git a/src/lib/Server/Admin/Fingerprint.py b/src/lib/Server/Admin/Fingerprint.py
new file mode 100644
index 000000000..bf50f0aaa
--- /dev/null
+++ b/src/lib/Server/Admin/Fingerprint.py
@@ -0,0 +1,19 @@
+'''Fingerprint mode for bcfg2-admin'''
+
+import Bcfg2.tlslite.api
+import Bcfg2.Server.Admin
+
+class Fingerprint(Bcfg2.Server.Admin.Mode):
+ '''Produce server key fingerprint'''
+ __shorthelp__ = 'bcfg2-admin fingerprint'
+ __longhelp__ = __shorthelp__ + '\n\tPrint the server certificate fingerprint'
+ def __call__(self, args):
+ Bcfg2.Server.Admin.Mode.__call__(self, args)
+ print self.getFingerprint()
+
+ def getFingerprint(self):
+ '''calculate key fingerprint'''
+ keypath = self.cfp.get('communication', 'key')
+ x509 = Bcfg2.tlslite.api.X509()
+ x509.parse(open(keypath).read())
+ return x509.getFingerprint()
diff --git a/src/lib/Server/Admin/Fingerprint.pyc b/src/lib/Server/Admin/Fingerprint.pyc
new file mode 100644
index 000000000..7bbe66d00
--- /dev/null
+++ b/src/lib/Server/Admin/Fingerprint.pyc
Binary files differ
diff --git a/src/lib/Server/Admin/Init.py b/src/lib/Server/Admin/Init.py
new file mode 100644
index 000000000..01bede88e
--- /dev/null
+++ b/src/lib/Server/Admin/Init.py
@@ -0,0 +1,119 @@
+import os, socket
+import Bcfg2.Server.Admin
+
+config = '''
+[server]
+repository = %s
+structures = Bundler,Base
+generators = SSHbase,Cfg,Pkgmgr,Rules
+
+[statistics]
+sendmailpath = /usr/sbin/sendmail
+database_engine = sqlite3
+# 'postgresql', 'mysql', 'mysql_old', 'sqlite3' or 'ado_mssql'.
+database_name =
+# Or path to database file if using sqlite3.
+#<repository>/etc/brpt.sqlite is default path if left empty
+database_user =
+# Not used with sqlite3.
+database_password =
+# Not used with sqlite3.
+database_host =
+# Not used with sqlite3.
+database_port =
+# Set to empty string for default. Not used with sqlite3.
+web_debug = True
+
+
+[communication]
+protocol = xmlrpc/ssl
+password = %s
+key = %s/bcfg2.key
+
+[components]
+bcfg2 = %s
+'''
+
+groups = '''
+<Groups version='3.0'>
+ <Group profile='true' public='false' default='true' name='basic'>
+ <Group name='%s'/>
+ </Group>
+ <Group name='ubuntu'/>
+ <Group name='debian'/>
+ <Group name='freebsd'/>
+ <Group name='gentoo'/>
+ <Group name='redhat'/>
+ <Group name='suse'/>
+ <Group name='mandrake'/>
+ <Group name='solaris'/>
+</Groups>
+'''
+clients = '''
+<Clients version="3.0">
+ <Client profile="basic" pingable="Y" pingtime="0" name="%s"/>
+</Clients>
+'''
+
+os_list = [('Redhat/Fedora/RHEL/RHAS/Centos', 'redhat'),
+ ('SUSE/SLES', 'suse'),
+ ('Mandrake', 'mandrake'),
+ ('Debian', 'debian'),
+ ('Ubuntu', 'ubuntu'),
+ ('Gentoo', 'gentoo'),
+ ('FreeBSD', 'freebsd')]
+
+
+class Init(Bcfg2.Server.Admin.Mode):
+ __shorthelp__ = 'bcfg2-admin init'
+ __longhelp__ = __shorthelp__ + '\n\tCompare two client specifications or directories of specifications'
+ def __call__(self, args):
+ Bcfg2.Server.Admin.Mode.__call__(self, args)
+ repopath = raw_input( "location of bcfg2 repository [/var/lib/bcfg2]: " )
+ if repopath == '':
+ repopath = '/var/lib/bcfg2'
+ password = ''
+ while ( password == '' ):
+ password = raw_input(
+ "Input password used for communication verification: " )
+ server = "https://%s:6789" % socket.getfqdn()
+ rs = raw_input( "Input the server location [%s]: " % server)
+ if rs:
+ server = rs
+ #create the groups.xml file
+ prompt = '''Input base Operating System for clients:\n'''
+ for entry in os_list:
+ prompt += "%d: \n" % (os_list.index(entry) + 1, entry[0])
+ prompt += ': '
+ os_sel = os_list[int(raw_input(prompt))-1][1]
+ self.initializeRepo(repopath, server, password, os_sel)
+ print "Repository created successfuly in %s" % (repopath)
+
+ def initializeRepo(self, repo, server_uri, password, os_selection):
+ '''Setup a new repo'''
+ keypath = os.path.dirname(os.path.abspath(self.configfile))
+ confdata = config % ( repo, password, keypath, server_uri )
+ open(self.configfile,"w").write(confdata)
+ # FIXME automate ssl key generation
+ os.popen('openssl req -x509 -nodes -days 1000 -newkey rsa:1024 -out %s/bcfg2.key -keyout %s/bcfg2.key' % (keypath, keypath))
+ try:
+ os.chmod('%s/bcfg2.key'% keypath,'0600')
+ except:
+ pass
+
+ for subdir in ['SSHbase', 'Cfg', 'Pkgmgr', 'Rules', 'etc', 'Metadata',
+ 'Base', 'Bundler']:
+ path = "%s/%s" % (repo, subdir)
+ newpath = ''
+ for subdir in path.split('/'):
+ newpath = newpath + subdir + '/'
+ try:
+ os.mkdir(newpath)
+ except:
+ continue
+
+ open("%s/Metadata/groups.xml"%repo, "w").write(groups % os_selection)
+ #now the clients file
+ open("%s/Metadata/clients.xml"%repo, "w").write(clients % socket.getfqdn())
+
+
diff --git a/src/lib/Server/Admin/Init.pyc b/src/lib/Server/Admin/Init.pyc
new file mode 100644
index 000000000..2cbb030c7
--- /dev/null
+++ b/src/lib/Server/Admin/Init.pyc
Binary files differ
diff --git a/src/lib/Server/Admin/Minestruct.py b/src/lib/Server/Admin/Minestruct.py
new file mode 100644
index 000000000..8f8be7e77
--- /dev/null
+++ b/src/lib/Server/Admin/Minestruct.py
@@ -0,0 +1,25 @@
+import Bcfg2.Server.Admin
+
+class Minestruct(Bcfg2.Server.Admin.Mode):
+ '''Pull extra entries out of statistics'''
+ __shorthelp__ = 'bcfg2-admin minestruct <client>'
+ __longhelp__ = __shorthelp__ + '\n\tExtract extra entry lists from statistics'
+ def __call__(self, args):
+ Bcfg2.Server.Admin.Mode.__call__(self, args)
+ if len(args) != 1:
+ self.errExit("minestruct must be called with a client name")
+ extra = self.MineStruct(args[1])
+ self.log.info("Found %d extra entries" % (len(extra)))
+ self.log.info(["%s: %s" % (entry.tag, entry.get('name')) for entry in extra])
+
+ def MineStruct(self, client):
+ '''Pull client entries into structure'''
+ stats = self.load_stats(client)
+ if len(stats.getchildren()) == 2:
+ # FIXME this is busted
+ # client is dirty
+ current = [ent for ent in stats.getchildren() if ent.get('state') == 'dirty'][0]
+ else:
+ current = stats.getchildren()[0]
+ return current.find('Extra').getchildren()
+
diff --git a/src/lib/Server/Admin/Minestruct.pyc b/src/lib/Server/Admin/Minestruct.pyc
new file mode 100644
index 000000000..6270f1ed2
--- /dev/null
+++ b/src/lib/Server/Admin/Minestruct.pyc
Binary files differ
diff --git a/src/lib/Server/Admin/Pull.py b/src/lib/Server/Admin/Pull.py
new file mode 100644
index 000000000..ab334dbf9
--- /dev/null
+++ b/src/lib/Server/Admin/Pull.py
@@ -0,0 +1,99 @@
+
+import binascii, lxml.etree, time
+import Bcfg2.Server.Admin
+
+class Pull(Bcfg2.Server.Admin.Mode):
+ '''Pull mode retrieves entries from clients and integrates the information into the repository'''
+ __shorthelp__ = 'bcfg2-admin pull <client> <entry type> <entry name>'
+ __longhelp__ = __shorthelp__ + '\n\tIntegrate configuration information from clients into the server repository'
+ def __init__(self, configfile):
+ Bcfg2.Server.Admin.Mode.__init__(self, configfile)
+
+ def __call__(self, args):
+ Bcfg2.Server.Admin.Mode.__call__(self, args)
+ self.PullEntry(args[0], args[1], args[2])
+
+ def PullEntry(self, client, etype, ename):
+ '''Make currently recorded client state correct for entry'''
+ # FIXME Pull.py is _way_ too interactive
+ sdata = self.load_stats(client)
+ if sdata.xpath('.//Statistics[@state="dirty"]'):
+ state = 'dirty'
+ else:
+ state = 'clean'
+ # need to pull entry out of statistics
+ sxpath = ".//Statistics[@state='%s']/Bad/ConfigFile[@name='%s']/../.." % (state, ename)
+ sentries = sdata.xpath(sxpath)
+ if not len(sentries):
+ self.errExit("Found %d entries for %s:%s:%s" % \
+ (len(sentries), client, etype, ename))
+ else:
+ print "Found %d entries for %s:%s:%s" % \
+ (len(sentries), client, etype, ename)
+ maxtime = max([time.strptime(stat.get('time')) for stat in sentries])
+ print "Found entry from", time.strftime("%c", maxtime)
+ statblock = [stat for stat in sentries \
+ if time.strptime(stat.get('time')) == maxtime]
+ entry = statblock[0].xpath('.//Bad/ConfigFile[@name="%s"]' % ename)
+ if not entry:
+ self.errExit("Could not find state data for entry; rerun bcfg2 on client system")
+ cfentry = entry[-1]
+
+ badfields = [field for field in ['perms', 'owner', 'group'] \
+ if cfentry.get(field) != cfentry.get('current_' + field) and \
+ cfentry.get('current_' + field)]
+ if badfields:
+ m_updates = dict([(field, cfentry.get('current_' + field)) \
+ for field in badfields])
+ print "got metadata_updates", m_updates
+ else:
+ m_updates = {}
+
+ if 'current_bdiff' in cfentry.attrib:
+ data = False
+ diff = binascii.a2b_base64(cfentry.get('current_bdiff'))
+ elif 'current_diff' in cfentry.attrib:
+ data = False
+ diff = cfentry.get('current_diff')
+ elif 'current_bfile' in cfentry.attrib:
+ data = binascii.a2b_base64(cfentry.get('current_bfile'))
+ diff = False
+ else:
+ if not m_updates:
+ self.errExit("having trouble processing entry. Entry is:\n" \
+ + lxml.etree.tostring(cfentry))
+ else:
+ data = False
+ diff = False
+
+ if diff:
+ print "Located diff:\n %s" % diff
+ elif data:
+ print "Found full (binary) file data"
+ if m_updates:
+ print "Found metadata updates"
+
+ if not diff and not data and not m_updates:
+ self.errExit("Failed to locate diff or full data or metadata updates\nStatistics entry was:\n%s" % lxml.etree.tostring(cfentry))
+
+ try:
+ bcore = Bcfg2.Server.Core.Core({}, self.configfile)
+ except Bcfg2.Server.Core.CoreInitError, msg:
+ self.errExit("Core load failed because %s" % msg)
+ [bcore.fam.Service() for _ in range(10)]
+ while bcore.fam.Service():
+ pass
+ m = bcore.metadata.get_metadata(client)
+ # find appropriate plugin in bcore
+ glist = [gen for gen in bcore.generators if
+ gen.Entries.get(etype, {}).has_key(ename)]
+ if len(glist) != 1:
+ self.errExit("Got wrong numbers of matching generators for entry:" \
+ + "%s" % ([g.__name__ for g in glist]))
+ plugin = glist[0]
+ try:
+ plugin.AcceptEntry(m, 'ConfigFile', ename, diff, data, m_updates)
+ except Bcfg2.Server.Plugin.PluginExecutionError:
+ self.errExit("Configuration upload not supported by plugin %s" \
+ % (plugin.__name__))
+ # svn commit if running under svn
diff --git a/src/lib/Server/Admin/Pull.pyc b/src/lib/Server/Admin/Pull.pyc
new file mode 100644
index 000000000..4b76524cc
--- /dev/null
+++ b/src/lib/Server/Admin/Pull.pyc
Binary files differ
diff --git a/src/lib/Server/Admin/Tidy.py b/src/lib/Server/Admin/Tidy.py
new file mode 100644
index 000000000..80307df80
--- /dev/null
+++ b/src/lib/Server/Admin/Tidy.py
@@ -0,0 +1,52 @@
+import Bcfg2.Server.Admin
+import re, os, socket
+
+class Tidy(Bcfg2.Server.Admin.Mode):
+ __shorthelp__ = 'bcfg2-admin tidy [-f] [-I]'
+ __longhelp__ = __shorthelp__ + '\n\tClean up useless files in the repo'
+ def __call__(self, args):
+ Bcfg2.Server.Admin.Mode.__call__(self, args)
+ badfiles = self.buildTidyList()
+ if '-f' in args or '-I' in args:
+ if '-I' in args:
+ for name in badfiles[:]:
+ answer = raw_input("Unlink file %s? [yN] " % name)
+ if answer not in ['y', 'Y']:
+ badfiles.remove(name)
+ for name in badfiles:
+ try:
+ os.unlink(name)
+ except IOError:
+ print "Failed to unlink %s" % name
+ else:
+ for name in badfiles:
+ print name
+
+ def buildTidyList(self):
+ '''Clean up unused or unusable files from the repository'''
+ hostmatcher = re.compile('.*\.H_(\S+)$')
+ repo = self.get_repo_path()
+ to_remove = []
+ good = []
+ bad = []
+
+ # clean up unresolvable hosts in SSHbase
+ for name in os.listdir("%s/SSHbase" % (repo)):
+ if hostmatcher.match(name):
+ hostname = hostmatcher.match(name).group(1)
+ if hostname in good + bad:
+ continue
+ try:
+ socket.gethostbyname(hostname)
+ good.append(hostname)
+ except:
+ bad.append(hostname)
+ for name in os.listdir("%s/SSHbase" % (repo)):
+ if not hostmatcher.match(name):
+ to_remove.append("%s/SSHbase/%s" % (repo, name))
+ else:
+ if hostmatcher.match(name).group(1) in bad:
+ to_remove.append("%s/SSHbase/%s" % (repo, name))
+ # clean up file~
+ # clean up files without parsable names in Cfg
+ return to_remove
diff --git a/src/lib/Server/Admin/Tidy.pyc b/src/lib/Server/Admin/Tidy.pyc
new file mode 100644
index 000000000..96cf089bd
--- /dev/null
+++ b/src/lib/Server/Admin/Tidy.pyc
Binary files differ
diff --git a/src/lib/Server/Admin/Viz.py b/src/lib/Server/Admin/Viz.py
new file mode 100644
index 000000000..bc2d8665c
--- /dev/null
+++ b/src/lib/Server/Admin/Viz.py
@@ -0,0 +1,143 @@
+
+import getopt, popen2, lxml.etree
+import Bcfg2.Server.Admin
+
+class Viz(Bcfg2.Server.Admin.Mode):
+ __shorthelp__ = '''bcfg2-admin viz [--includehosts] [--includebundles] [--includekey] [-o output.png] [--raw]'''
+ __longhelp__ = __shorthelp__ + '\n\tProduce graphviz diagrams of metadata structures'
+ def __init__(self, configfile):
+ Bcfg2.Server.Admin.Mode.__init__(self, configfile)
+ self.colors = ['steelblue1', 'chartreuse', 'gold', 'magenta',
+ 'indianred1', 'limegreen', 'orange1', 'lightblue2',
+ 'green1', 'blue1', 'yellow1', 'darkturquoise', 'gray66']
+
+ def __call__(self, args):
+ Bcfg2.Server.Admin.Mode.__call__(self, args)
+ # First get options to the 'viz' subcommand
+ try:
+ opts, args = getopt.getopt(args, 'rhbko:',
+ ['raw', 'includehosts', 'includebundles',
+ 'includekey', 'outfile='])
+ except getopt.GetoptError, msg:
+ print msg
+ raise SystemExit(1)
+
+ rset = False
+ hset = False
+ bset = False
+ kset = False
+ outputfile = False
+ for opt, arg in opts:
+ if opt in ("-r", "--raw"):
+ rset = True
+ elif opt in ("-h", "--includehosts"):
+ hset = True
+ elif opt in ("-b", "--includebundles"):
+ bset = True
+ elif opt in ("-k", "--includekey"):
+ kset = True
+ elif opt in ("-o", "--outfile"):
+ outputfile = arg
+ repopath = self.get_repo_path()
+
+ data = self.Visualize(repopath, rset, hset, bset, kset)
+ if outputfile:
+ open(outputfile, 'w').write(data)
+ else:
+ print data
+
+ def Visualize(self, repopath, raw=False, hosts=False,
+ bundles=False, key=False):
+ '''Build visualization of groups file'''
+ groupdata = lxml.etree.parse(repopath + '/Metadata/groups.xml')
+ groupdata.xinclude()
+ groups = groupdata.getroot()
+ if raw:
+ dotpipe = popen2.Popen4("dd bs=4M 2>/dev/null")
+ else:
+ dotpipe = popen2.Popen4("dot -Tpng")
+ categories = {'default':'grey83'}
+ instances = {}
+ egroups = groups.findall("Group") + groups.findall('.//Groups/Group')
+ for group in egroups:
+ if group.get('category', False):
+ if not categories.has_key(group.get('category')):
+ categories[group.get('category')] = self.colors.pop()
+
+ try:
+ dotpipe.tochild.write("digraph groups {\n")
+ except:
+ print "write to dot process failed. Is graphviz installed?"
+ raise SystemExit(1)
+ dotpipe.tochild.write('\trankdir="LR";\n')
+
+ if hosts:
+ clients = lxml.etree.parse(repopath + \
+ '/Metadata/clients.xml').getroot()
+ for client in clients.findall('Client'):
+ if instances.has_key(client.get('profile')):
+ instances[client.get('profile')].append(client.get('name'))
+ else:
+ instances[client.get('profile')] = [client.get('name')]
+ for profile, clist in instances.iteritems():
+ clist.sort()
+ dotpipe.tochild.write(
+ '''\t"%s-instances" [ label="%s", shape="record" ];\n''' \
+ % (profile, '|'.join(clist)))
+ dotpipe.tochild.write('''\t"%s-instances" -> "group-%s";\n''' \
+ % (profile, profile))
+
+ if bundles:
+ bundles = []
+ [bundles.append(bund.get('name')) \
+ for bund in groups.findall('.//Bundle')
+ if bund.get('name') not in bundles]
+ bundles.sort()
+ for bundle in bundles:
+ dotpipe.tochild.write(
+ '''\t"bundle-%s" [ label="%s", shape="septagon"];\n''' \
+ % (bundle, bundle))
+ gseen = []
+ for group in egroups:
+ color = categories[group.get('category', 'default')]
+ if group.get('profile', 'false') == 'true':
+ style = "filled, bold"
+ else:
+ style = "filled"
+ gseen.append(group.get('name'))
+ dotpipe.tochild.write(
+ '\t"group-%s" [label="%s", style="%s", fillcolor=%s];\n' %
+ (group.get('name'), group.get('name'), style, color))
+ if bundles:
+ for bundle in group.findall('Bundle'):
+ dotpipe.tochild.write('\t"group-%s" -> "bundle-%s";\n' %
+ (group.get('name'), bundle.get('name')))
+
+ gfmt = '\t"group-%s" [label="%s", style="filled", fillcolor="grey83"];\n'
+ for group in egroups:
+ for parent in group.findall('Group'):
+ if parent.get('name') not in gseen:
+ dotpipe.tochild.write(gfmt % (parent.get('name'),
+ parent.get('name')))
+ gseen.append(parent.get("name"))
+ dotpipe.tochild.write('\t"group-%s" -> "group-%s" ;\n' %
+ (group.get('name'), parent.get('name')))
+
+ if key:
+ dotpipe.tochild.write("\tsubgraph cluster_key {\n")
+ dotpipe.tochild.write('''\tstyle="filled";\n''')
+ dotpipe.tochild.write('''\tcolor="lightblue";\n''')
+ dotpipe.tochild.write('''\tBundle [ shape="septagon" ];\n''')
+ dotpipe.tochild.write('''\tGroup [shape="ellipse"];\n''')
+ dotpipe.tochild.write('''\tProfile [style="bold", shape="ellipse"];\n''')
+ dotpipe.tochild.write('''\tHblock [label="Host1|Host2|Host3", shape="record"];\n''')
+ for category in categories:
+ dotpipe.tochild.write(
+ '''\t''' + category + ''' [label="''' + category + \
+ '''", shape="record", style="filled", fillcolor=''' + \
+ categories[category] + '''];\n''')
+ dotpipe.tochild.write('''\tlabel="Key";\n''')
+ dotpipe.tochild.write("\t}\n")
+ dotpipe.tochild.write("}\n")
+ dotpipe.tochild.close()
+ return dotpipe.fromchild.read()
diff --git a/src/lib/Server/Admin/Viz.pyc b/src/lib/Server/Admin/Viz.pyc
new file mode 100644
index 000000000..b403fd4e9
--- /dev/null
+++ b/src/lib/Server/Admin/Viz.pyc
Binary files differ
diff --git a/src/lib/Server/Admin/__init__.py b/src/lib/Server/Admin/__init__.py
new file mode 100644
index 000000000..d059e0a1d
--- /dev/null
+++ b/src/lib/Server/Admin/__init__.py
@@ -0,0 +1,45 @@
+__revision__ = '$Revision: $'
+
+__all__ = ['Mode', 'Client', 'Compare', 'Fingerprint', 'Init', 'Minestruct',
+ 'Pull', 'Tidy', 'Viz']
+
+import ConfigParser, lxml.etree, logging
+
+class Mode(object):
+ '''Help message has not yet been added for mode'''
+ __shorthelp__ = 'Shorthelp not defined yet'
+ __longhelp__ = 'Longhelp not defined yet'
+ __args__ = []
+ def __init__(self, configfile):
+ self.configfile = configfile
+ self.__cfp = False
+ self.log = logging.getLogger('Bcfg2.Server.Admin.Mode')
+
+ def getCFP(self):
+ if not self.__cfp:
+ self.__cfp = ConfigParser.ConfigParser()
+ self.__cfp.read(self.configfile)
+ return self.__cfp
+
+ cfp = property(getCFP)
+
+ def __call__(self, args):
+ if args[0] == 'help':
+ print self.__longhelp__
+ raise SystemExit(0)
+
+ def errExit(self, emsg):
+ print emsg
+ raise SystemExit(1)
+
+ def get_repo_path(self):
+ '''return repository path'''
+ return self.cfp.get('server', 'repository')
+
+ def load_stats(self, client):
+ stats = lxml.etree.parse("%s/etc/statistics.xml" % (self.get_repo_path()))
+ hostent = stats.xpath('//Node[@name="%s"]' % client)
+ if not hostent:
+ self.errExit("Could not find stats for client %s" % (client))
+ return hostent[0]
+
diff --git a/src/lib/Server/Admin/__init__.pyc b/src/lib/Server/Admin/__init__.pyc
new file mode 100644
index 000000000..d09de4f96
--- /dev/null
+++ b/src/lib/Server/Admin/__init__.pyc
Binary files differ
diff --git a/src/lib/Server/__init__.py b/src/lib/Server/__init__.py
index c6743f58b..8f9b7eaf5 100644
--- a/src/lib/Server/__init__.py
+++ b/src/lib/Server/__init__.py
@@ -2,5 +2,6 @@
'''This is the set of modules for Bcfg2.Server'''
__revision__ = '$Revision$'
-__all__ = ["Core", "Plugin", "Plugins", "Statistics", "Hostbase", "Reports"]
+__all__ = ["Admin", "Core", "Plugin", "Plugins", "Statistics",
+ "Hostbase", "Reports"]