summaryrefslogtreecommitdiffstats
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
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
-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
-rwxr-xr-xsrc/sbin/bcfg2-admin643
20 files changed, 712 insertions, 613 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"]
diff --git a/src/sbin/bcfg2-admin b/src/sbin/bcfg2-admin
index dab67bf96..d7ad02d3a 100755
--- a/src/sbin/bcfg2-admin
+++ b/src/sbin/bcfg2-admin
@@ -2,588 +2,27 @@
'''bcfg2-admin is a script that helps to administrate a bcfg2 deployment'''
import getopt, difflib, logging, lxml.etree, os, popen2, re, socket, sys, ConfigParser
-import Bcfg2.Server.Core, Bcfg2.Logging, Bcfg2.tlslite.api
-import binascii, time
+import Bcfg2.Server.Core, Bcfg2.Logging
log = logging.getLogger('bcfg-admin')
-colors = ['steelblue1', 'chartreuse', 'gold', 'magenta', 'indianred1', 'limegreen',
- 'orange1', 'lightblue2', 'green1', 'blue1', 'yellow1', 'darkturquoise',
- 'gray66']
+import Bcfg2.Server.Admin
-usage = '''
-bcfg2-admin [options]
-fingerprint - print the server certificate fingerprint
-init - initialize the bcfg2 repository
- (this is interactive; only run once)
-pull <client> <entry type> <entry name>
- - mine statistics for entry information
-minestruct <client>
- - mine statistics for extra entries
-viz [--includehosts] [--includebundles] [--includekey]
- [-o output.png] [--raw]
-client add name= profile= uuid= password= address= secure= location=
-tidy - clean up unused files from repo
-compare <config1.xml> <config2.xml>
- - compare two configurations for differences
-'''
+def mode_import(modename):
+ '''Load Bcfg2.Server.Admin.<mode>'''
+ modname = modename.capitalize()
+ mod = getattr(__import__("Bcfg2.Server.Admin.%s" %
+ (modname)).Server.Admin, modname)
+ return getattr(mod, modname)
-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>
-'''
-
-prompt = '''
-please select which operating system your machine is running:
-a. Redhat/Fedora/RHEL/RHAS/Centos
-b. SUSE/SLES
-c. Mandrake
-d. Debian
-e. Ubuntu
-f. Solaris
-g. Gentoo
-h. FreeBSD
-'''
-
-def err_exit(emsg):
- print emsg
- raise SystemExit(1)
-
-#build bcfg2.conf file
-def initialize_repo(cfile):
- '''Setup a new repo'''
- repo = raw_input( "location of bcfg2 repository [/var/lib/bcfg2]: " )
- if repo == '':
- repo = '/var/lib/bcfg2'
-
- password = ''
- while ( password == '' ):
- password = raw_input( "please provide the password used for communication verification: " )
-
- #get the hostname
- server = "https://%s:6789" % socket.getfqdn()
- uri = raw_input( "please provide the server location[%s]: " % server)
- if uri == '':
- uri = server
-
- #guess path of ssl key file
- keypath = os.path.dirname(os.path.abspath(cfile))
-
- try:
- open(cfile,"w").write(config % ( repo, password, keypath, uri ))
- except:
- err_exit("Failed to write bcfg2 config file to %s" % cfile)
-
- #generate the ssl key
- print "Now we will generate the ssl key used for secure communitcation"
- 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
-
- #create the repo dirs
- 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:
- pass
-
- #create the groups.xml file
- selection = ''
- while ( selection == '' ):
- print prompt
- selection = raw_input(" selection: ")
- if selection.lower() not in 'abcdefgh':
- selection = ''
- if selection.lower() == 'a':
- selection = 'redhat'
- elif selection.lower() == 'b':
- selection = 'suse'
- elif selection.lower() == 'c':
- selection = 'mandrake'
- elif selection.lower() == 'd':
- selection = 'debian'
- elif selection.lower() == 'e':
- selection = 'ubuntu'
- elif selection.lower() == 'f':
- selection = 'solaris'
- elif selection.lower() == 'g':
- selection = 'gentoo'
- elif selection.lower() == 'h':
- selection = 'freebsd'
-
- open("%s/Metadata/groups.xml"%repo, "w").write(groups%selection)
-
- #now the clients file
- open("%s/Metadata/clients.xml"%repo, "w").write(clients%socket.getfqdn())
- print "Repository created successfuly in %s" % (repo)
-
-def get_repo_path(cfile='/etc/bcfg2.conf'):
- '''return repository path'''
- cfp = ConfigParser.ConfigParser()
- cfp.read(cfile)
- return cfp.get('server', 'repository')
-
-def load_stats(repo, client):
- stats = lxml.etree.parse("%s/etc/statistics.xml" % (repo))
- hostent = stats.xpath('//Node[@name="%s"]' % client)
- if not hostent:
- err_exit("Could not find stats for client %s" % (client))
- return hostent[0]
-
-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 compare(new, old):
- for child in new.getchildren():
- equiv = old.xpath('%s[@name="%s"]' % (child.tag, child.get('name')))
- if not important.has_key(child.tag):
- 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 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 do_compare(cargs):
- '''run file comparison'''
- if '-r' in cargs:
- cargs.remove('-r')
- (oldd, newd) = args
- (old, new) = [os.listdir(spot) for spot in args]
- for item in old:
- print "Entry:", item
- state = do_compare([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) = cargs
- except IndexError:
- print "Usage: bcfg2-admin compare <old> <new>"
- raise SystemExit
-
- try:
- new = lxml.etree.parse(new).getroot()
- except IOError:
- print "Failed to read %s" % (new)
- raise SystemExit(1)
-
- try:
- old = lxml.etree.parse(old).getroot()
- except IOError:
- print "Failed to read %s" % (old)
- 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 compare(bundle, equiv[0]):
- new.remove(bundle)
- old.remove(equiv[0])
- rcs.append(True)
- else:
- rcs.append(False)
- else:
- print "dunno what is going on for bundle %s" % (bundle.get('name'))
- i1 = new.find('./Independant')
- i2 = old.find('./Independant')
- if compare(i1, i2):
- new.remove(i1)
- old.remove(i2)
- else:
- rcs.append(False)
- return not False in rcs
-
-def do_fingerprint(cfile):
- '''calculate key fingerprint'''
- cf = ConfigParser.ConfigParser()
- cf.read([cfile])
- keypath = cf.get('communication', 'key')
- x509 = Bcfg2.tlslite.api.X509()
- x509.parse(open(keypath).read())
- print x509.getFingerprint()
-
-def do_pull(cfile, repopath, client, etype, ename):
- '''Make currently recorded client state correct for entry'''
- sdata = load_stats(repopath, 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)
- print "Found %d entries for %s:%s:%s" % \
- (len(sentries), client, etype, ename)
- if not len(sentries):
- raise SystemExit, 1
- 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:
- err_exit("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:
- print "having trouble processing entry. Entry is:"
- print lxml.etree.tostring(cfentry)
- raise SystemExit, 1
- 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:
- err_exit("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({}, cfile)
- except Bcfg2.Server.Core.CoreInitError, msg:
- print "Core load failed because %s" % msg
- raise SystemExit(1)
- [bcore.fam.Service() for x 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:
- err_exit("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:
- err_exit("Configuration upload not supported by plugin %s" \
- % (plugin.__name__))
- # svn commit if running under svn
-
-def do_minestruct(repopath, argdata):
- '''Pull client entries into structure'''
- if len(argdata) != 1:
- err_exit("minestruct must be called with a client name")
- client = argdata[0]
- stats = load_stats(repopath, client)
- if len(stats.getchildren()) == 2:
- # client is dirty
- current = [ent for ent in stats.getchildren() if ent.get('state') == 'dirty'][0]
- else:
- current = stats.getchildren()[0]
- extra = current.find('Extra').getchildren()
- log.info("Found %d extra entries" % (len(extra)))
- log.info(["%s: %s" % (entry.tag, entry.get('name')) for entry in extra])
-
-def do_tidy(repo, args):
- '''Clean up unused or unusable files from the repository'''
- hostmatcher = re.compile('.*\.H_(\S+)$')
- score = ([], [])
- # clean up unresolvable hosts in SSHbase
- for name in os.listdir("%s/SSHbase" % (repo)):
- if not hostmatcher.match(name):
- print "could not match name %s" % (name)
- else:
- hostname = hostmatcher.match(name).group(1)
- if hostname in score[0] + score[1]:
- continue
- try:
- socket.gethostbyname(hostname)
- score[0].append(hostname)
- except:
- print "could not resolve %s" % (hostname)
- score[1].append(hostname)
- for name in os.listdir("%s/SSHbase" % (repo)):
- if not hostmatcher.match(name):
- print "could not match name %s" % (name)
- else:
- if hostmatcher.match(name).group(1) in score[1]:
- if '-f' in args:
- os.unlink("%s/SSHbase/%s" % (repo, name))
- else:
- answer = raw_input("Unlink file %s? [yN] " % name)
- if answer in ['y', 'Y']:
- os.unlink("%s/SSHbase/%s" % (repo, name))
- # clean up file~
- # clean up files without parsable names in Cfg
-
-def do_viz(repopath, myargs):
- '''Build visualization of groups file'''
- # First get options to the 'viz' subcommand
- try:
- opts, args = getopt.getopt(myargs, 'rhbko:', ['raw', 'includehosts', 'includebundles', 'includekey', 'outfile='])
- except getopt.GetoptError, msg:
- print msg
- raise SystemExit(1)
-
- options = []
- for opt, arg in opts:
- if opt in ("-r", "--raw"):
- options.append("raw")
- elif opt in ("-h", "--includehosts"):
- options.append("hosts")
- elif opt in ("-b", "--includebundles"):
- options.append("bundles")
- elif opt in ("-k", "--includekey"):
- options.append("key")
- elif opt in ("-o", "--outfile"):
- options.append("outfile")
- outputfile = arg
-
- groupdata = lxml.etree.parse(repopath + '/Metadata/groups.xml')
- groupdata.xinclude()
- groups = groupdata.getroot()
- if 'raw' in options:
- 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')] = 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' in options:
- 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' in options:
- 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' in options:
- for bundle in group.findall('Bundle'):
- dotpipe.tochild.write('\t"group-%s" -> "bundle-%s";\n' %
- (group.get('name'), bundle.get('name')))
-
- for group in egroups:
- for parent in group.findall('Group'):
- if parent.get('name') not in gseen:
- dotpipe.tochild.write('\t"group-%s" [label="%s", style="filled", fillcolor="grey83"];\n' %
- (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' in options:
- 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()
- data = dotpipe.fromchild.read()
- if 'outfile' in options:
- output = open(outputfile, 'w').write(data)
- else:
- print data
-
-def do_client(repopath, args):
- '''Do things with clients'''
- tree = lxml.etree.parse(repopath + '/Metadata/clients.xml')
- root = tree.getroot()
-
- if args[0] == 'add':
- # Adding a node
- print "Adding client..."
- element = lxml.etree.Element("Client")
- for i in args[1:]:
- attr, val = i.split('=', 1)
- if not(attr in ['name', 'profile', 'uuid', 'password', 'address', 'secure', 'location']):
- print "Attribute %s unknown" % attr
- raise SystemExit(1)
- element.attrib[attr] = val
- root.append(element)
-
- elif args[0] in ['delete', 'remove']:
- # Removing a node
- print "Removing"
-
- tree.write(repopath + '/Metadata/clients.xml')
- print "Done"
-
if __name__ == '__main__':
- Bcfg2.Logging.setup_logging('bcfg2-admin', to_console=False)
+ Bcfg2.Logging.setup_logging('bcfg2-admin', to_console=True)
# Some sensible defaults
configfile = "/etc/bcfg2.conf"
- Repopath = ""
try:
- opts, args = getopt.getopt(sys.argv[1:], 'hC:R:', ['help', 'configfile=', 'repopath='])
+ opts, args = getopt.getopt(sys.argv[1:], 'hC:', ['help', 'configfile='])
except getopt.GetoptError, msg:
print msg
raise SystemExit(1)
@@ -592,51 +31,31 @@ if __name__ == '__main__':
print usage
raise SystemExit(1)
+ help = False
# First get the options...
for opt, arg in opts:
if opt in ("-h", "--help"):
- print usage
- raise SystemExit(1)
+ help = True
if opt in ("-C", "--configfile"):
configfile = arg
- if opt in ("-R", "--repopath"):
- Repopath = arg
-
- # ...then do something with the other arguments
- if Repopath == '' and 'init' not in args:
- Repopath = get_repo_path(configfile)
-
- if len(args) < 1:
- print usage
-
- elif args[0] == "init":
- initialize_repo(configfile)
-
- elif args[0] == 'pull':
- if len(args) != 4:
- print usage
- raise SystemExit(1)
- do_pull(configfile, Repopath, args[1], args[2], args[3])
-
- elif args[0] == 'minestruct':
- do_minestruct(Repopath, args[1:])
-
- elif args[0] == 'tidy':
- do_tidy(Repopath, args[1:])
- elif args[0] == 'viz':
- do_viz(Repopath, args[1:])
-
- elif args[0] == 'compare':
- do_compare(args[1:])
-
- elif args[0] == 'fingerprint':
- do_fingerprint(configfile)
+ modes = [x.lower() for x in Bcfg2.Server.Admin.__all__]
+ modes.remove('mode')
+ if help or len(args) < 1 or args[0] == 'help':
+ if len(args) > 1:
+ args = [args[1], args[0]]
+ else:
+ for mod in [mode_import(mode) for mode in modes]:
+ print mod.__shorthelp__
+ raise SystemExit(0)
+ if args[0] in modes:
+ modname = args[0].capitalize()
+ try:
+ mode_cls = mode_import(modname)
+ except ImportError, e:
+ log.error("Failed to load admin mod %s: %s" % (modname, e))
+ mode = mode_cls(configfile)
+ mode(args[1:])
+ else:
+ print "unknown mode %s" % args[0]
- elif args[0] == 'client':
- if len(args) < 4:
- print usage
- raise SystemExit(1)
- do_client(Repopath, args[1:])
- else:
- print usage