From 3610288cbcbf4d1adedefa03166cd77ee15aad96 Mon Sep 17 00:00:00 2001 From: Narayan Desai Date: Sat, 29 Dec 2007 04:57:53 +0000 Subject: 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 --- src/lib/Server/Admin/Client.py | 46 +++ src/lib/Server/Admin/Client.pyc | Bin 0 -> 2173 bytes src/lib/Server/Admin/Compare.py | 131 +++++++ src/lib/Server/Admin/Compare.pyc | Bin 0 -> 4806 bytes src/lib/Server/Admin/Fingerprint.py | 19 ++ src/lib/Server/Admin/Fingerprint.pyc | Bin 0 -> 1298 bytes src/lib/Server/Admin/Init.py | 119 +++++++ src/lib/Server/Admin/Init.pyc | Bin 0 -> 4346 bytes src/lib/Server/Admin/Minestruct.py | 25 ++ src/lib/Server/Admin/Minestruct.pyc | Bin 0 -> 1686 bytes src/lib/Server/Admin/Pull.py | 99 ++++++ src/lib/Server/Admin/Pull.pyc | Bin 0 -> 4122 bytes src/lib/Server/Admin/Tidy.py | 52 +++ src/lib/Server/Admin/Tidy.pyc | Bin 0 -> 2067 bytes src/lib/Server/Admin/Viz.py | 143 ++++++++ src/lib/Server/Admin/Viz.pyc | Bin 0 -> 5610 bytes src/lib/Server/Admin/__init__.py | 45 +++ src/lib/Server/Admin/__init__.pyc | Bin 0 -> 2485 bytes src/lib/Server/__init__.py | 3 +- src/sbin/bcfg2-admin | 643 ++--------------------------------- 20 files changed, 712 insertions(+), 613 deletions(-) create mode 100644 src/lib/Server/Admin/Client.py create mode 100644 src/lib/Server/Admin/Client.pyc create mode 100644 src/lib/Server/Admin/Compare.py create mode 100644 src/lib/Server/Admin/Compare.pyc create mode 100644 src/lib/Server/Admin/Fingerprint.py create mode 100644 src/lib/Server/Admin/Fingerprint.pyc create mode 100644 src/lib/Server/Admin/Init.py create mode 100644 src/lib/Server/Admin/Init.pyc create mode 100644 src/lib/Server/Admin/Minestruct.py create mode 100644 src/lib/Server/Admin/Minestruct.pyc create mode 100644 src/lib/Server/Admin/Pull.py create mode 100644 src/lib/Server/Admin/Pull.pyc create mode 100644 src/lib/Server/Admin/Tidy.py create mode 100644 src/lib/Server/Admin/Tidy.pyc create mode 100644 src/lib/Server/Admin/Viz.py create mode 100644 src/lib/Server/Admin/Viz.pyc create mode 100644 src/lib/Server/Admin/__init__.py create mode 100644 src/lib/Server/Admin/__init__.pyc (limited to 'src') 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 attr1=val1 attr2=val2\nbcfg2-admin client del ' + __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 Binary files /dev/null and b/src/lib/Server/Admin/Client.pyc 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 \nbcfg2-admin compare -r " + __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 Binary files /dev/null and b/src/lib/Server/Admin/Compare.pyc 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 Binary files /dev/null and b/src/lib/Server/Admin/Fingerprint.pyc 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. +#/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 = ''' + + + + + + + + + + + + + +''' +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 Binary files /dev/null and b/src/lib/Server/Admin/Init.pyc 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 ' + __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 Binary files /dev/null and b/src/lib/Server/Admin/Minestruct.pyc 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 ' + __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 Binary files /dev/null and b/src/lib/Server/Admin/Pull.pyc 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 Binary files /dev/null and b/src/lib/Server/Admin/Tidy.pyc 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 Binary files /dev/null and b/src/lib/Server/Admin/Viz.pyc 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 Binary files /dev/null and b/src/lib/Server/Admin/__init__.pyc 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 - - mine statistics for entry information -minestruct - - 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 - - compare two configurations for differences -''' +def mode_import(modename): + '''Load Bcfg2.Server.Admin.''' + 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. -#/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 = ''' - - - - - - - - - - - - - -''' -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 " - 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 -- cgit v1.2.3-1-g7c22