summaryrefslogtreecommitdiffstats
path: root/build/scripts-2.7
diff options
context:
space:
mode:
Diffstat (limited to 'build/scripts-2.7')
-rwxr-xr-xbuild/scripts-2.7/bcfg2313
-rwxr-xr-xbuild/scripts-2.7/bcfg2-admin80
-rwxr-xr-xbuild/scripts-2.7/bcfg2-build-reports319
-rwxr-xr-xbuild/scripts-2.7/bcfg2-info468
-rwxr-xr-xbuild/scripts-2.7/bcfg2-ping-sweep71
-rwxr-xr-xbuild/scripts-2.7/bcfg2-repo-validate227
-rwxr-xr-xbuild/scripts-2.7/bcfg2-reports282
-rwxr-xr-xbuild/scripts-2.7/bcfg2-server79
8 files changed, 1839 insertions, 0 deletions
diff --git a/build/scripts-2.7/bcfg2 b/build/scripts-2.7/bcfg2
new file mode 100755
index 000000000..9f56481bf
--- /dev/null
+++ b/build/scripts-2.7/bcfg2
@@ -0,0 +1,313 @@
+#!/usr/bin/python
+
+"""Bcfg2 Client"""
+__revision__ = '$Revision$'
+
+import logging
+import os
+import signal
+import sys
+import tempfile
+import time
+import xmlrpclib
+import fcntl
+import Bcfg2.Options
+import Bcfg2.Client.XML
+import Bcfg2.Client.Frame
+import Bcfg2.Client.Tools
+
+import Bcfg2.Proxy
+import Bcfg2.Logger
+
+logger = logging.getLogger('bcfg2')
+
+def cb_sigint_handler(signum, frame):
+ """Exit upon CTRL-C."""
+ os._exit(1)
+
+DECISION_LIST = Bcfg2.Options.Option('Decision List', default=False,
+ cmd="--decision-list", odesc='<file>',
+ long_arg=True)
+
+
+class Client:
+ """The main bcfg2 client class"""
+
+ def __init__(self):
+ self.toolset = None
+ self.config = None
+
+ optinfo = {
+ # 'optname': (('-a', argdesc, optdesc),
+ # env, cfpath, default, boolean)),
+ 'verbose': Bcfg2.Options.VERBOSE,
+ 'extra': Bcfg2.Options.CLIENT_EXTRA_DISPLAY,
+ 'quick': Bcfg2.Options.CLIENT_QUICK,
+ 'debug': Bcfg2.Options.DEBUG,
+ 'lockfile': Bcfg2.Options.LOCKFILE,
+ 'drivers': Bcfg2.Options.CLIENT_DRIVERS,
+ 'dryrun': Bcfg2.Options.CLIENT_DRYRUN,
+ 'paranoid': Bcfg2.Options.CLIENT_PARANOID,
+ 'bundle': Bcfg2.Options.CLIENT_BUNDLE,
+ 'bundle-quick': Bcfg2.Options.CLIENT_BUNDLEQUICK,
+ 'indep': Bcfg2.Options.CLIENT_INDEP,
+ 'file': Bcfg2.Options.CLIENT_FILE,
+ 'interactive': Bcfg2.Options.INTERACTIVE,
+ 'cache': Bcfg2.Options.CLIENT_CACHE,
+ 'profile': Bcfg2.Options.CLIENT_PROFILE,
+ 'remove': Bcfg2.Options.CLIENT_REMOVE,
+ 'help': Bcfg2.Options.HELP,
+ 'setup': Bcfg2.Options.CFILE,
+ 'server': Bcfg2.Options.SERVER_LOCATION,
+ 'user': Bcfg2.Options.CLIENT_USER,
+ 'password': Bcfg2.Options.SERVER_PASSWORD,
+ 'retries': Bcfg2.Options.CLIENT_RETRIES,
+ 'kevlar': Bcfg2.Options.CLIENT_KEVLAR,
+ 'decision-list': DECISION_LIST,
+ 'encoding': Bcfg2.Options.ENCODING,
+ 'omit-lock-check': Bcfg2.Options.OMIT_LOCK_CHECK,
+ 'filelog': Bcfg2.Options.LOGGING_FILE_PATH,
+ 'decision': Bcfg2.Options.CLIENT_DLIST,
+ 'servicemode': Bcfg2.Options.CLIENT_SERVICE_MODE,
+ 'key': Bcfg2.Options.CLIENT_KEY,
+ 'certificate': Bcfg2.Options.CLIENT_CERT,
+ 'ca': Bcfg2.Options.CLIENT_CA,
+ 'serverCN': Bcfg2.Options.CLIENT_SCNS,
+ }
+
+ self.setup = Bcfg2.Options.OptionParser(optinfo)
+ self.setup.parse(sys.argv[1:])
+
+ if self.setup['args']:
+ print("Bcfg2 takes no arguments, only options")
+ print(self.setup.buildHelpMessage())
+ raise SystemExit(1)
+ level = 30
+ if self.setup['verbose']:
+ level = 20
+ if self.setup['debug']:
+ level = 0
+ Bcfg2.Logger.setup_logging('bcfg2',
+ to_syslog=False,
+ level=level,
+ to_file=self.setup['filelog'])
+ self.logger = logging.getLogger('bcfg2')
+ self.logger.debug(self.setup)
+ if self.setup['bundle-quick']:
+ if self.setup['bundle'] == []:
+ self.logger.error("-Q option requires -b")
+ raise SystemExit(1)
+ elif self.setup['remove'] != False:
+ self.logger.error("-Q option incompatible with -r")
+ raise SystemExit(1)
+ if 'drivers' in self.setup and self.setup['drivers'] == 'help':
+ self.logger.info("The following drivers are available:")
+ self.logger.info(Bcfg2.Client.Tools.drivers)
+ raise SystemExit(0)
+ if self.setup['remove'] and 'services' in self.setup['remove']:
+ self.logger.error("Service removal is nonsensical, disable services to get former behavior")
+ if self.setup['remove'] not in [False, 'all', 'services', 'packages']:
+ self.logger.error("Got unknown argument %s for -r" % (self.setup['remove']))
+ if (self.setup["file"] != False) and (self.setup["cache"] != False):
+ print("cannot use -f and -c together")
+ raise SystemExit(1)
+
+ def run_probe(self, probe):
+ """Execute probe."""
+ name = probe.get('name')
+ self.logger.info("Running probe %s" % name)
+ ret = Bcfg2.Client.XML.Element("probe-data",
+ name=name,
+ source=probe.get('source'))
+ try:
+ scripthandle, scriptname = tempfile.mkstemp()
+ script = open(scriptname, 'w+')
+ try:
+ script.write("#!%s\n" %
+ (probe.attrib.get('interpreter', '/bin/sh')))
+ script.write(probe.text)
+ script.close()
+ os.close(scripthandle)
+ os.chmod(script.name, 0755)
+ ret.text = os.popen(script.name).read().strip()
+ self.logger.info("Probe %s has result:\n%s" % (name, ret.text))
+ finally:
+ os.unlink(script.name)
+ except:
+ self.logger.error("Failed to execute probe: %s" % (name), exc_info=1)
+ raise SystemExit(1)
+ return ret
+
+ def fatal_error(self, message):
+ """Signal a fatal error."""
+ self.logger.error("Fatal error: %s" % (message))
+ os._exit(1)
+
+ def run(self):
+ """Perform client execution phase."""
+ times = {}
+
+ # begin configuration
+ times['start'] = time.time()
+
+ if self.setup['file']:
+ # read config from file
+ try:
+ self.logger.debug("Reading cached configuration from %s" %
+ (self.setup['file']))
+ configfile = open(self.setup['file'], 'r')
+ rawconfig = configfile.read()
+ configfile.close()
+ except IOError:
+ self.fatal_error("Failed to read cached configuration from: %s"
+ % (self.setup['file']))
+ return(1)
+ else:
+ # retrieve config from server
+ proxy = Bcfg2.Proxy.ComponentProxy(self.setup['server'],
+ self.setup['user'],
+ self.setup['password'],
+ key = self.setup['key'],
+ cert = self.setup['certificate'],
+ ca = self.setup['ca'],
+ allowedServerCNs = self.setup['serverCN'])
+
+ if self.setup['profile']:
+ try:
+ proxy.AssertProfile(self.setup['profile'])
+ except xmlrpclib.Fault:
+ self.fatal_error("Failed to set client profile")
+ return(1)
+
+ try:
+ probe_data = proxy.GetProbes()
+ except xmlrpclib.Fault, flt:
+ self.logger.error("Failed to download probes from bcfg2")
+ self.logger.error(flt.faultString)
+ raise SystemExit(1)
+
+ times['probe_download'] = time.time()
+
+ try:
+ probes = Bcfg2.Client.XML.XML(probe_data)
+ except Bcfg2.Client.XML.ParseError, syntax_error:
+ self.fatal_error(
+ "Server returned invalid probe requests: %s" %
+ (syntax_error))
+ return(1)
+
+ # execute probes
+ try:
+ probedata = Bcfg2.Client.XML.Element("ProbeData")
+ [probedata.append(self.run_probe(probe))
+ for probe in probes.findall(".//probe")]
+ except:
+ self.logger.error("Failed to execute probes")
+ raise SystemExit(1)
+
+ if len(probes.findall(".//probe")) > 0:
+ try:
+ # upload probe responses
+ proxy.RecvProbeData(Bcfg2.Client.XML.tostring(probedata, encoding='UTF-8', xml_declaration=True))
+ except:
+ self.logger.error("Failed to upload probe data", exc_info=1)
+ raise SystemExit(1)
+
+ times['probe_upload'] = time.time()
+
+ if self.setup['decision'] in ['whitelist', 'blacklist']:
+ try:
+ self.setup['decision_list'] = proxy.GetDecisionList( \
+ self.setup['decision'])
+ self.logger.info("Got decision list from server:")
+ self.logger.info(self.setup['decision_list'])
+ except xmlrpclib.Fault, f:
+ if f.faultCode == 1:
+ print("GetDecisionList method not supported by server")
+ else:
+ self.logger.error("Failed to de", exc_info=1)
+ raise SystemExit(1)
+
+ try:
+ rawconfig = proxy.GetConfig()
+ except xmlrpclib.Fault:
+ self.logger.error("Failed to download configuration from Bcfg2")
+ raise SystemExit(2)
+
+ times['config_download'] = time.time()
+
+ if self.setup['cache']:
+ try:
+ open(self.setup['cache'], 'w').write(rawconfig.encode(self.setup['encoding']))
+ os.chmod(self.setup['cache'], 33152)
+ except IOError:
+ self.logger.warning("Failed to write config cache file %s" %
+ (self.setup['cache']))
+ times['caching'] = time.time()
+
+ try:
+ self.config = Bcfg2.Client.XML.XML(rawconfig)
+ except Bcfg2.Client.XML.ParseError, syntax_error:
+ self.fatal_error("The configuration could not be parsed: %s" %
+ (syntax_error))
+ return(1)
+
+ times['config_parse'] = time.time()
+
+ if self.config.tag == 'error':
+ self.fatal_error("Server error: %s" % (self.config.text))
+ return(1)
+
+ if self.setup['bundle-quick']:
+ newconfig = Bcfg2.Client.XML.XML('<Configuration/>')
+ [newconfig.append(bundle) for bundle in self.config.getchildren() if \
+ bundle.tag == 'Bundle' and bundle.get('name') in self.setup['bundle']]
+ self.config = newconfig
+
+ self.tools = Bcfg2.Client.Frame.Frame(self.config,
+ self.setup,
+ times, self.setup['drivers'],
+ self.setup['dryrun'])
+
+ if not self.setup['omit-lock-check']:
+ #check lock here
+ try:
+ lockfile = open(self.setup['lockfile'], 'w')
+ try:
+ fcntl.lockf(lockfile.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
+ except IOError:
+ #otherwise exit and give a warning to the user
+ self.fatal_error("An other instance of Bcfg2 is running. If you what to bypass the check, run with %s option" %
+ (Bcfg2.Options.OMIT_LOCK_CHECK.cmd))
+ except:
+ lockfile = None
+ self.logger.error("Failed to open lockfile")
+ # execute the said configuration
+ self.tools.Execute()
+
+ if not self.setup['omit-lock-check']:
+ #unlock here
+ if lockfile:
+ try:
+ fcntl.lockf(lockfile.fileno(), fcntl.LOCK_UN)
+ os.remove(self.setup['lockfile'])
+ except OSError:
+ self.logger.error("Failed to unlock lockfile %s" % lockfile.name)
+
+ if not self.setup['file'] and not self.setup['bundle-quick']:
+ # upload statistics
+ feedback = self.tools.GenerateStats()
+
+ try:
+ proxy.RecvStats(Bcfg2.Client.XML.tostring(feedback,
+ encoding='UTF-8',
+ xml_declaration=True))
+ except xmlrpclib.Fault:
+ self.logger.error("Failed to upload configuration statistics")
+ raise SystemExit(2)
+
+if __name__ == '__main__':
+ signal.signal(signal.SIGINT, cb_sigint_handler)
+ client = Client()
+ spid = os.getpid()
+ client.run()
diff --git a/build/scripts-2.7/bcfg2-admin b/build/scripts-2.7/bcfg2-admin
new file mode 100755
index 000000000..4d5ab2e5b
--- /dev/null
+++ b/build/scripts-2.7/bcfg2-admin
@@ -0,0 +1,80 @@
+#!/usr/bin/python
+"""bcfg2-admin is a script that helps to administrate a Bcfg2 deployment."""
+
+from optparse import OptionParser
+from StringIO import StringIO
+import logging
+import Bcfg2.Server.Core
+import Bcfg2.Logger
+import Bcfg2.Options
+
+log = logging.getLogger('bcfg2-admin')
+
+import Bcfg2.Server.Admin
+
+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)
+
+def get_modes():
+ """Get all available modes, except for the base mode."""
+ return [x.lower() for x in Bcfg2.Server.Admin.__all__ if x != 'mode']
+
+def create_description():
+ """Create the description string from the list of modes."""
+ modes = get_modes()
+ description = StringIO()
+ description.write("Available modes are:\n\n")
+ for mode in modes:
+ try:
+ description.write((" %-15s %s\n" %
+ (mode, mode_import(mode).__shorthelp__)))
+ except ImportError:
+ continue
+ return description.getvalue()
+
+def main():
+ Bcfg2.Logger.setup_logging('bcfg2-admin', to_console=True, level=40)
+ usage = "Usage: %prog [options] MODE [args]"
+ parser = OptionParser(usage=usage)
+ parser.set_defaults(configfile=Bcfg2.Options.CFILE.default)
+ parser.add_option("-C", "--configfile",
+ dest="configfile",
+ help="Path to bcfg2.conf",
+ metavar="FILE")
+ parser.disable_interspersed_args()
+ (options, args) = parser.parse_args()
+
+ # Provide help if requested or no args were specified
+ if len(args) < 1 or args[0] == 'help':
+ if len(args) > 1:
+ # Get help for a specific mode by passing it the help argument
+ args = [args[1], args[0]]
+ else:
+ # Print short help for all modes
+ parser.print_help()
+ print create_description()
+ raise SystemExit(0)
+
+ if args[0] in get_modes():
+ modname = args[0].capitalize()
+ try:
+ mode_cls = mode_import(modname)
+ except ImportError, e:
+ log.error("Failed to load admin mode %s: %s" % (modname, e))
+ raise SystemExit(1)
+ mode = mode_cls(options.configfile)
+ mode(args[1:])
+ if hasattr(mode, 'bcore'):
+ mode.bcore.shutdown()
+ else:
+ log.error("Unknown mode %s" % args[0])
+ parser.print_help()
+ print create_description()
+ raise SystemExit(1)
+
+if __name__ == '__main__':
+ main()
diff --git a/build/scripts-2.7/bcfg2-build-reports b/build/scripts-2.7/bcfg2-build-reports
new file mode 100755
index 000000000..575a1b7a4
--- /dev/null
+++ b/build/scripts-2.7/bcfg2-build-reports
@@ -0,0 +1,319 @@
+#!/usr/bin/python
+
+"""
+bcfg2-build-reports generates & distributes reports of statistic
+information for Bcfg2."""
+
+__revision__ = '$Revision$'
+
+import copy
+import getopt
+import re
+import os
+import socket
+import sys
+from time import asctime, strptime
+from ConfigParser import ConfigParser, NoSectionError, NoOptionError
+from lxml.etree import XML, XSLT, parse, Element, ElementTree, SubElement, tostring, XMLSyntaxError
+
+def generatereport(rspec, nrpt):
+ """
+ generatereport creates and returns an ElementTree representation
+ of a report adhering to the XML spec for intermediate reports.
+ """
+ reportspec = copy.deepcopy(rspec)
+ nodereprt = copy.deepcopy(nrpt)
+
+ reportgood = reportspec.get("good", default = 'Y')
+ reportmodified = reportspec.get("modified", default = 'Y')
+ current_date = asctime()[:10]
+
+ """Build regex of all the nodes we are reporting about."""
+ pattern = re.compile( '|'.join([item.get("name") for item in reportspec.findall('Machine')]))
+
+ for node in nodereprt.findall('Node'):
+ if not (node.findall("Statistics") and pattern.match(node.get('name'))):
+ # Don't know enough about node.
+ nodereprt.remove(node)
+ continue
+
+ # Reduce to most recent Statistics entry.
+ statisticslist = node.findall('Statistics')
+ # This line actually sorts from most recent to oldest.
+ statisticslist.sort(lambda y, x: cmp(strptime(x.get("time")), strptime(y.get("time"))))
+ stats = statisticslist[0]
+
+ [node.remove(item) for item in node.findall('Statistics')]
+
+ # Add a good tag if node is good and we wnat to report such.
+ if reportgood == 'Y' and stats.get('state') == 'clean':
+ SubElement(stats,"Good")
+
+ [stats.remove(item) for item in stats.findall("Bad") + stats.findall("Modified") if \
+ item.getchildren() == []]
+ [stats.remove(item) for item in stats.findall("Modified") if reportmodified == 'N']
+
+ # Test for staleness -if stale add Stale tag.
+ if stats.get("time").find(current_date) == -1:
+ SubElement(stats,"Stale")
+ node.append(stats)
+ return nodereprt
+
+def mail(mailbody, confi):
+ """mail mails a previously generated report."""
+
+ try:
+ mailer = confi.get('statistics', 'sendmailpath')
+ except (NoSectionError, NoOptionError):
+ mailer = "/usr/sbin/sendmail"
+ # Open a pipe to the mail program and
+ # write the data to the pipe.
+ pipe = os.popen("%s -t" % mailer, 'w')
+ pipe.write(mailbody)
+ exitcode = pipe.close()
+ if exitcode:
+ print "Exit code: %s" % exitcode
+
+def rss(reportxml, delivery, report):
+ """rss appends a new report to the specified rss file
+ keeping the last 9 articles.
+ """
+ # Check and see if rss file exists.
+ for destination in delivery.findall('Destination'):
+ try:
+ fil = open(destination.attrib['address'], 'r')
+ olddoc = XML(fil.read())
+
+ # Defines the number of recent articles to keep.
+ items = olddoc.find("channel").findall("item")[0:9]
+ fil.close()
+ fil = open(destination.attrib['address'], 'w')
+ except (IOError, XMLSyntaxError):
+ fil = open(destination.attrib['address'], 'w')
+ items = []
+
+ rssdata = Element("rss")
+ channel = SubElement(rssdata, "channel")
+ rssdata.set("version", "2.0")
+ chantitle = SubElement(channel, "title")
+ chantitle.text = report.attrib['name']
+ chanlink = SubElement(channel, "link")
+
+ # This can later link to WWW report if one gets published
+ # simultaneously?
+ chanlink.text = "http://www.mcs.anl.gov/cobalt/bcfg2"
+ chandesc = SubElement(channel, "description")
+ chandesc.text = "Information regarding the 10 most recent bcfg2 runs."
+
+ channel.append(XML(reportxml))
+
+ if items != []:
+ for item in items:
+ channel.append(item)
+
+ tree = tostring(rssdata, encoding='UTF-8', xml_declaration=True)
+ fil.write(tree)
+ fil.close()
+
+def www(reportxml, delivery):
+ """www outputs report to."""
+
+ # This can later link to WWW report if one gets published
+ # simultaneously?
+ for destination in delivery.findall('Destination'):
+ fil = open(destination.attrib['address'], 'w')
+
+ fil.write(reportxml)
+ fil.close()
+
+def fileout(reportxml, delivery):
+ """Outputs to plain text file."""
+ for destination in delivery.findall('Destination'):
+ fil = open(destination.attrib['address'], 'w')
+
+ fil.write(reportxml)
+ fil.close()
+
+def pretty_print(element, level=0):
+ """Produce a pretty-printed text representation of element."""
+ if element.text:
+ fmt = "%s<%%s %%s>%%s</%%s>" % (level*" ")
+ data = (element.tag, (" ".join(["%s='%s'" % keyval for keyval in element.attrib.iteritems()])),
+ element.text, element.tag)
+ if element._children:
+ fmt = "%s<%%s %%s>\n" % (level*" ",) + (len(element._children) * "%s") + "%s</%%s>\n" % (level*" ")
+ data = (element.tag, ) + (" ".join(["%s='%s'" % keyval for keyval in element.attrib.iteritems()]),)
+ data += tuple([pretty_print(entry, level+2) for entry in element._children]) + (element.tag, )
+ else:
+ fmt = "%s<%%s %%s/>\n" % (level * " ")
+ data = (element.tag, " ".join(["%s='%s'" % keyval for keyval in element.attrib.iteritems()]))
+ return fmt % data
+
+
+if __name__ == '__main__':
+ ping=True
+ all=False
+ if '-C' in sys.argv:
+ cfpath = sys.argv[sys.argv.index('-C') + 1]
+ else:
+ cfpath = '/etc/bcfg2.conf'
+ c = ConfigParser()
+ c.read([cfpath])
+ configpath = "%s/etc/report-configuration.xml" % c.get('server', 'repository')
+ statpath = "%s/etc/statistics.xml" % c.get('server', 'repository')
+ clientsdatapath = "%s/Metadata/clients.xml" % c.get('server', 'repository')
+ try:
+ prefix = c.get('server', 'prefix')
+ except (NoSectionError, NoOptionError):
+ prefix = '/usr'
+
+ transformpath = "/%s/share/bcfg2/xsl-transforms/" % (prefix)
+ #websrcspath = "/usr/share/bcfg2/web-rprt-srcs/"
+
+ try:
+ opts, args = getopt.getopt(sys.argv[1:], "C:hAc:Ns:", ["help", "all", "config=","no-ping", "stats="])
+ except getopt.GetoptError, mesg:
+ # Print help information and exit:
+ print "%s\nUsage:\nbcfg2-build-reports [-h][-A (include ALL clients)] [-c <configuration-file>] [-s <statistics-file>][-N (do not ping clients)]" % (mesg)
+ raise SystemExit, 2
+ for o, a in opts:
+ if o in ("-h", "--help"):
+ print "Usage:\nbcfg2-build-reports [-h] [-c <configuration-file>] [-s <statistics-file>]"
+ raise SystemExit
+ if o in ("-A", "--all"):
+ all=True
+ if o in ("-c", "--config"):
+ configpath = a
+ if o in ("-N", "--no-ping"):
+ ping = False
+ if o in ("-s", "--stats"):
+ statpath = a
+
+
+ # See if hostinfo.xml exists, and is less than 23.5 hours old
+ #try:
+ #hostinstat = os.stat(hostinfopath)
+ #if (time() - hostinstat[9])/(60*60) > 23.5:
+ if ping:
+ os.system('bcfg2-ping-sweep -C %s' % cfpath) # bcfg2-ping-sweep needs to be in path
+ #except OSError:
+ # os.system('GenerateHostInfo')#Generate HostInfo needs to be in path
+
+
+ """Reads data & config files."""
+ try:
+ statsdata = XML(open(statpath).read())
+ except (IOError, XMLSyntaxError):
+ print("bcfg2-build-reports: Failed to parse %s"%(statpath))
+ raise SystemExit, 1
+ try:
+ configdata = XML(open(configpath).read())
+ except (IOError, XMLSyntaxError):
+ print("bcfg2-build-reports: Failed to parse %s"%(configpath))
+ raise SystemExit, 1
+ try:
+ clientsdata = XML(open(clientsdatapath).read())
+ except (IOError, XMLSyntaxError):
+ print("bcfg2-build-reports: Failed to parse %s"%(clientsdatapath))
+ raise SystemExit, 1
+
+ # Merge data from three sources.
+ nodereport = Element("Report", attrib={"time" : asctime()})
+ # Should all of the other info in Metadata be appended?
+ # What about all of the package stuff for other types of reports?
+ for client in clientsdata.findall("Client"):
+ nodel = Element("Node", attrib={"name" : client.get("name")})
+ nodel.append(client)
+ for nod in statsdata.findall("Node"):
+ if client.get('name').find(nod.get('name')) == 0:
+ for statel in nod.findall("Statistics"):
+ nodel.append(statel)
+ nodereport.append(nodel)
+
+ if all:
+ for nod in statsdata.findall("Node"):
+ for client in clientsdata.findall("Client"):
+ if client.get('name').find(nod.get('name')) == 0:
+ break
+ else:
+ nodel = Element("Node", attrib={"name" : nod.get("name")})
+ client = Element("Client", attrib={"name" : nod.get("name"), "profile" : "default"})
+ nodel.append(client)
+ for statel in nod.findall("Statistics"):
+ nodel.append(statel)
+ nodereport.append(nodel)
+
+
+ for reprt in configdata.findall('Report'):
+ nodereport.set("name", reprt.get("name", default="BCFG Report"))
+
+ if reprt.get('refresh-time') != None:
+ nodereport.set("refresh-time", reprt.get("refresh-time", default="600"))
+
+ procnodereport = generatereport(reprt, nodereport)
+
+ for deliv in reprt.findall('Delivery'):
+ # Is a deepcopy of procnodereport necessary?
+
+ delivtype = deliv.get('type', default='nodes-digest')
+ deliverymechanism = deliv.get('mechanism', default='www')
+
+ # Apply XSLT, different ones based on report type, and options
+ if deliverymechanism == 'null-operator': # Special Cases
+ fileout(tostring(ElementTree(procnodereport).getroot(), encoding='UTF-8', xml_declaration=True), deliv)
+ break
+ transform = delivtype + '-' + deliverymechanism + '.xsl'
+
+ try: # Make sure valid stylesheet is selected.
+ os.stat(transformpath + transform)
+ except:
+ print("bcfg2-build-reports: Invalid report type or delivery mechanism.\n Can't find: "\
+ + transformpath + transform)
+ raise SystemExit, 1
+
+ try: # Try to parse stylesheet.
+ stylesheet = XSLT(parse(transformpath + transform))
+ except:
+ print("bcfg2-build-reports: invalid XSLT transform file.")
+ raise SystemExit, 1
+
+ if deliverymechanism == 'mail':
+ if delivtype == 'nodes-individual':
+ reportdata = copy.deepcopy(procnodereport)
+ for noden in reportdata.findall("Node"):
+ [reportdata.remove(y) for y in reportdata.findall("Node")]
+ reportdata.append(noden)
+ result = stylesheet.apply(ElementTree(reportdata))
+ outputstring = stylesheet.tostring(result)
+
+ if not outputstring == None:
+ toastring = ''
+ for desti in deliv.findall("Destination"):
+ toastring = "%s%s " % \
+ (toastring, desti.get('address'))
+ # Prepend To: and From:
+ outputstring = "To: %s\nFrom: root@%s\n%s"% \
+ (toastring, socket.getfqdn(), outputstring)
+ mail(outputstring, c) #call function to send
+
+ else:
+ reportdata = copy.deepcopy(procnodereport)
+
+ result = stylesheet.apply(ElementTree(reportdata))
+ outputstring = stylesheet.tostring(result)
+
+ if not outputstring == None:
+ toastring = ''
+ for desti in deliv.findall("Destination"):
+ toastring = "%s%s " % \
+ (toastring, desti.get('address'))
+ # Prepend To: and From:
+ outputstring = "To: %s\nFrom: root@%s\n%s"% \
+ (toastring, socket.getfqdn(), outputstring)
+ mail(outputstring, c) #call function to send
+ else:
+ outputstring = tostring(stylesheet.apply(ElementTree(procnodereport)).getroot(), encoding='UTF-8', xml_declaration=True)
+ if deliverymechanism == 'rss':
+ rss(outputstring, deliv, reprt)
+ else: # Must be deliverymechanism == 'www':
+ www(outputstring, deliv)
diff --git a/build/scripts-2.7/bcfg2-info b/build/scripts-2.7/bcfg2-info
new file mode 100755
index 000000000..9721122f3
--- /dev/null
+++ b/build/scripts-2.7/bcfg2-info
@@ -0,0 +1,468 @@
+#!/usr/bin/python
+
+"""This tool loads the Bcfg2 core into an interactive debugger."""
+__revision__ = '$Revision$'
+
+from code import InteractiveConsole
+import cmd
+import errno
+import getopt
+import logging
+import lxml.etree
+import os
+import sys
+import tempfile
+
+try:
+ import profile
+ import pstats
+ have_profile = True
+except:
+ have_profile = False
+
+import Bcfg2.Logger
+import Bcfg2.Options
+import Bcfg2.Server.Core
+import Bcfg2.Server.Plugins.Metadata
+import Bcfg2.Server.Plugin
+
+logger = logging.getLogger('bcfg2-info')
+
+class dummyError(Exception):
+ pass
+
+class FileNotBuilt(Exception):
+ """Thrown when File entry contains no content."""
+ def __init__(self, value):
+ Exception.__init__(self)
+ self.value = value
+ def __str__(self):
+ return repr(self.value)
+
+def printTabular(rows):
+ """Print data in tabular format."""
+ cmax = tuple([max([len(str(row[index])) for row in rows]) + 1 \
+ for index in range(len(rows[0]))])
+ fstring = (" %%-%ss |" * len(cmax)) % cmax
+ fstring = ('|'.join([" %%-%ss "] * len(cmax))) % cmax
+ print(fstring % rows[0])
+ print((sum(cmax) + (len(cmax) * 2) + (len(cmax) - 1)) * '=')
+ for row in rows[1:]:
+ print(fstring % row)
+
+def displayTrace(trace, num=80, sort=('time', 'calls')):
+ stats = pstats.Stats(trace)
+ stats.sort_stats('cumulative', 'calls', 'time')
+ stats.print_stats(200)
+
+def write_config_file(outputdir, cfg):
+ """Store file content of an <ConfigFile name='/path/to/file' ...>...</ConfigFile> entry
+ in the appropriate directory under the output directory.
+ """
+ name = cfg.get('name')
+
+ # directory creation
+ try:
+ os.makedirs(os.path.dirname(outputdir + name))
+ except OSError, err:
+ if err.errno != errno.EEXIST:
+ raise
+ except:
+ raise
+
+ # write config file
+ config_file = open(outputdir + name, "w")
+ try:
+ config_file.write(cfg.text)
+ except: # plugin throw an exception and therefore there is no content => None
+ raise FileNotBuilt(name)
+ config_file.close()
+
+class infoCore(cmd.Cmd, Bcfg2.Server.Core.Core):
+
+ def __init__(self, repo, plgs, passwd, encoding, event_debug):
+ cmd.Cmd.__init__(self)
+ try:
+ Bcfg2.Server.Core.Core.__init__(self, repo, plgs, passwd,
+ encoding)
+ if event_debug:
+ self.fam.debug = True
+ except Bcfg2.Server.Core.CoreInitError, msg:
+ print("Core load failed because %s" % msg)
+ raise SystemExit(1)
+ self.prompt = '> '
+ self.cont = True
+ self.fam.handle_events_in_interval(4)
+
+ def do_loop(self):
+ self.cont = True
+ while self.cont:
+ try:
+ self.cmdloop('Welcome to bcfg2-info\n'
+ 'Type "help" for more information')
+ except SystemExit, val:
+ raise
+ except Bcfg2.Server.Plugin.PluginExecutionError:
+ continue
+ except KeyboardInterrupt:
+ print("Ctrl-C pressed exiting...")
+ self.do_exit([])
+ except dummyError:
+ continue
+ except:
+ logger.error("command failure", exc_info=1)
+
+ def do_debug(self, args):
+ try:
+ opts, _ = getopt.getopt(args.split(), 'nf:')
+ except:
+ print "Usage: debug [-n] [-f <command list>]"
+ return
+ self.cont = False
+ scriptmode = False
+ interactive = True
+ for opt in opts:
+ if opt[0] == '-f':
+ scriptmode = True
+ spath = opt[1]
+ elif opt[0] == '-n':
+ interactive = False
+ sh = InteractiveConsole(locals())
+ if scriptmode:
+ for command in [c.strip() for c in open(spath).readlines()]:
+ if command:
+ sh.push(command)
+ if interactive:
+ print("dropping to python interpreter; press ^D to resume")
+ try:
+ import IPython
+ shell = IPython.Shell.IPShell(argv=[], user_ns=locals())
+ shell.mainloop()
+ except ImportError:
+ sh.interact()
+
+ def do_quit(self, _):
+ """
+ Exit program.
+ Usage: [quit|exit]
+ """
+ for plugin in self.plugins.values():
+ plugin.shutdown()
+ os._exit(0)
+
+ do_EOF = do_quit
+ do_exit = do_quit
+
+ def do_help(self, _):
+ """Print out usage info."""
+ print 'Commands:'
+ print 'build <hostname> <filename> - build config for hostname, writing to filename'
+ print 'builddir <hostname> <dirname> - build config for hostname, writing separate files to dirname'
+ print 'buildall <directory> - build configs for all clients in directory'
+ print 'buildfile <filename> <hostname> - build config file for hostname (not written to disk)'
+ print 'bundles - print out group/bundle information'
+ print 'clients - print out client/profile information'
+ print 'debug - shell out to native python interpreter'
+ print 'event_debug - display filesystem events as they are processed'
+ print 'generators - list current versions of generators'
+ print 'groups - list groups'
+ print 'help - print this list of available commands'
+ print 'mappings <type*> <name*> - print generator mappings for optional type and name'
+ print 'profile <command> <args> - profile a single bcfg2-info command'
+ print 'quit - Exit the bcfg2-info command line'
+ print 'showentries <hostname> <type> - show abstract configuration entries for a given host'
+ print 'showclient <client1> <client2> - show metadata for given hosts'
+ print 'update - process pending file events'
+ print 'version - print version of this tool'
+
+
+ def do_update(self, _):
+ """Process pending fs events."""
+ self.fam.handle_events_in_interval(0.1)
+
+ def do_version(self, _):
+ """Print out code version."""
+ print(__revision__)
+
+ def do_build(self, args):
+ """Build client configuration."""
+ alist = args.split()
+ path_force = False
+ if '-f' in args:
+ alist.remove('-f')
+ path_force = True
+ if len(alist) == 2:
+ client, ofile = alist
+ if not ofile.startswith('/tmp') and not path_force:
+ print("Refusing to write files outside of /tmp without -f option")
+ return
+ output = open(ofile, 'w')
+ data = lxml.etree.tostring(self.BuildConfiguration(client),
+ encoding='UTF-8', xml_declaration=True,
+ pretty_print=True)
+ output.write(data)
+ output.close()
+ else:
+ print('Usage: build [-f] <hostname> <output file>')
+
+ def help_builddir(self):
+ """Display help for builddir command."""
+ print('Usage: builddir [-f] <hostname> <output dir>')
+ print('')
+ print('Generates a config for client <hostname> and writes the')
+ print('individual configuration files out separately in a tree')
+ print('under <output dir>. The <output dir> directory must be')
+ print('rooted under /tmp unless the -f argument is provided, in')
+ print('which case it can be located anywhere.')
+ print('')
+ print('NOTE: Currently only handles ConfigFile entries and writes')
+ print('all content with the default owner and permissions. These')
+ print('could be much more permissive than would be created by the')
+ print('bcfg2 client itself.')
+
+ def do_builddir(self, args):
+ """Build client configuration as separate files within a dir."""
+ alist = args.split()
+ path_force = False
+ if '-f' in args:
+ alist.remove('-f')
+ path_force = True
+ if len(alist) == 2:
+ client, odir = alist
+ if not odir.startswith('/tmp') and not path_force:
+ print("Refusing to write files outside of /tmp without -f option")
+ return
+ client_config = self.BuildConfiguration(client)
+ if client_config.tag == 'error':
+ print("Building client configuration failed.")
+ return
+
+ # handle <Path type='file'> entries
+ for configfile in [cfile for cfile in client_config.findall(".//Path[@type = 'file']")]:
+ try:
+ write_config_file(odir, configfile)
+ except FileNotBuilt, ex:
+ print("Warning: No file content generated for ConfigFile %s!" % ex)
+ pass
+ except Exception, ex:
+ print("unknown error, I give up: %s" %ex)
+ return
+
+ print("Config for %s written to %s" % (client, odir))
+
+ else:
+ print('Error: Incorrect number of parameters')
+ self.help_builddir()
+
+ def do_buildall(self, args):
+ if len(args.split()) != 1:
+ print("Usage: buildall <directory>")
+ return
+ try:
+ os.mkdir(args)
+ except:
+ pass
+ for client in self.metadata.clients:
+ self.do_build("%s %s/%s.xml" % (client, args, client))
+
+ def do_buildfile(self, args):
+ """Build a config file for client."""
+ if len(args.split()) == 2:
+ fname, client = args.split()
+ entry = lxml.etree.Element('Path', type='file', name=fname)
+ metadata = self.build_metadata(client)
+ self.Bind(entry, metadata)
+ print(lxml.etree.tostring(entry, encoding="UTF-8", xml_declaration=True))
+ else:
+ print('Usage: buildfile filename hostname')
+
+ def do_bundles(self, _):
+ """Print out group/bundle info."""
+ data = [('Group', 'Bundles')]
+ groups = list(self.metadata.groups.keys())
+ groups.sort()
+ for group in groups:
+ data.append((group,
+ ','.join(self.metadata.groups[group][0])))
+ printTabular(data)
+
+ def do_clients(self, _):
+ """Print out client info."""
+ data = [('Client', 'Profile')]
+ clist = list(self.metadata.clients.keys())
+ clist.sort()
+ for client in clist:
+ data.append((client, self.metadata.clients[client]))
+ printTabular(data)
+
+ def do_generators(self, _):
+ """Print out generator info."""
+ for generator in self.generators:
+ print(generator.__version__)
+
+ def do_showentries(self, args):
+ """Show abstract configuration entries for a given host."""
+ arglen = len(args.split())
+ if arglen not in [1, 2]:
+ print("Usage: showentries <hostname> <type>")
+ return
+ client = args.split()[0]
+ try:
+ meta = self.build_metadata(client)
+ except Bcfg2.Server.Plugins.Metadata.MetadataConsistencyError:
+ print("Unable to find metadata for host %s" % client)
+ return
+ structures = self.GetStructures(meta)
+ output = [('entrytype', 'name')]
+ if arglen == 1:
+ for item in structures:
+ for child in item.getchildren():
+ output.append((child.tag, child.get('name')))
+ if arglen == 2:
+ etype = args.split()[1]
+ for item in structures:
+ for child in item.getchildren():
+ if child.tag in [etype, "Bound%s" % etype]:
+ output.append((child.tag, child.get('name')))
+ printTabular(output)
+
+ def do_groups(self, _):
+ """Print out group info."""
+ data = [("Groups", "Profile", "Category", "Contains")]
+ grouplist = list(self.metadata.groups.keys())
+ grouplist.sort()
+ for group in grouplist:
+ if group in self.metadata.profiles:
+ prof = 'yes'
+ else:
+ prof = 'no'
+ if group in self.metadata.categories:
+ cat = self.metadata.categories[group]
+ else:
+ cat = ''
+ gdata = [grp for grp in self.metadata.groups[group][1]]
+ if group in gdata:
+ gdata.remove(group)
+ data.append((group, prof, cat, ','.join(gdata)))
+ printTabular(data)
+
+ def do_showclient(self, args):
+ """Print host metadata."""
+ data = [('Client', 'Profile', "Groups", "Bundles")]
+ if not len(args):
+ print("Usage:\nshowclient <client> ... <clientN>")
+ return
+ for client in args.split():
+ try:
+ client_meta = self.build_metadata(client)
+ except:
+ print("Client %s not defined" % client)
+ continue
+ print "Hostname:\t", client_meta.hostname
+ print "Profile:\t", client_meta.profile
+ print "Groups:\t\t", list(client_meta.groups)[0]
+ for grp in list(client_meta.groups)[1:]:
+ print '\t\t%s' % grp
+ if client_meta.bundles:
+ print "Bundles:\t", list(client_meta.bundles)[0]
+ for bnd in list(client_meta.bundles)[1:]:
+ print '\t\t%s' % bnd
+ if client_meta.connectors:
+ print "Connector data"
+ print "=" * 80
+ for conn in client_meta.connectors:
+ if getattr(client_meta, conn):
+ print "%s:\t" % (conn), getattr(client_meta, conn)
+ print "=" * 80
+
+ def do_mappings(self, args):
+ """Print out mapping info."""
+ # dump all mappings unless type specified
+ data = [('Plugin', 'Type', 'Name')]
+ arglen = len(args.split())
+ for generator in self.generators:
+ if arglen == 0:
+ etypes = list(generator.Entries.keys())
+ else:
+ etypes = [args.split()[0]]
+ if arglen == 2:
+ interested = [(etype, [args.split()[1]]) \
+ for etype in etypes]
+ else:
+ interested = [(etype, generator.Entries[etype]) \
+ for etype in etypes \
+ if etype in generator.Entries]
+ for etype, names in interested:
+ for name in [name for name in names if name in \
+ generator.Entries.get(etype, {})]:
+ data.append((generator.name, etype, name))
+ printTabular(data)
+
+ def do_event_debug(self, args):
+ self.fam.debug = True
+
+ def do_cfgdebug(self, args):
+ try:
+ meta = self.build_metadata(args)
+ except Bcfg2.Server.Plugins.Metadata.MetadataConsistencyError:
+ print("Unable to find metadata for host %s" % client)
+ return
+ structures = self.GetStructures(meta)
+ for clist in [struct.findall('Path') for struct in structures]:
+ for cfile in clist:
+ if cfile.get('name') in self.plugins['Cfg'].Entries['ConfigFile']:
+ cset = self.plugins['Cfg'].entries[cfile.get('name')]
+ cand = cset.get_matching(meta)
+ fields = ['all', 'group']
+ while len(cand) > 1 and fields:
+ field = fields.pop(0)
+ [cand.remove(c) for c in cand[:]
+ if getattr(c.specific, field)]
+ if len(cand) != 1:
+ sys.stderr.write("Entry %s failed" % cfile.get('name'))
+ continue
+ print(cand[0].name)
+
+ def do_profile(self, arg):
+ if not have_profile:
+ print("Profiling functionality not available")
+ return
+ tracefname = tempfile.mktemp()
+ p = profile.Profile()
+ p.runcall(self.onecmd, arg)
+ displayTrace(p)
+
+ def Run(self, args):
+ if args:
+ self.onecmd(" ".join(args))
+ os._exit(0)
+ else:
+ self.do_loop()
+
+if __name__ == '__main__':
+ Bcfg2.Logger.setup_logging('bcfg2-info', to_syslog=False)
+ optinfo = {
+ 'configfile': Bcfg2.Options.CFILE,
+ 'help': Bcfg2.Options.HELP,
+ }
+ optinfo.update({'repo': Bcfg2.Options.SERVER_REPOSITORY,
+ 'plugins': Bcfg2.Options.SERVER_PLUGINS,
+ 'password': Bcfg2.Options.SERVER_PASSWORD,
+ 'event debug': Bcfg2.Options.DEBUG,
+ 'profile': Bcfg2.Options.CORE_PROFILE,
+ 'encoding': Bcfg2.Options.ENCODING})
+ setup = Bcfg2.Options.OptionParser(optinfo)
+ setup.parse(sys.argv[1:])
+ if setup['profile'] and have_profile:
+ prof = profile.Profile()
+ loop = prof.runcall(infoCore, setup['repo'], setup['plugins'],
+ setup['password'], setup['encoding'],
+ setup['event debug'])
+ displayTrace(prof)
+ else:
+ if setup['profile']:
+ print("Profiling functionality not available")
+ loop = infoCore(setup['repo'], setup['plugins'], setup['password'],
+ setup['encoding'], setup['event debug'])
+
+ loop.Run(setup['args'])
diff --git a/build/scripts-2.7/bcfg2-ping-sweep b/build/scripts-2.7/bcfg2-ping-sweep
new file mode 100755
index 000000000..b3905e3a0
--- /dev/null
+++ b/build/scripts-2.7/bcfg2-ping-sweep
@@ -0,0 +1,71 @@
+#!/usr/bin/python
+#GenerateHostInfo - Joey Hagedorn - hagedorn@mcs.anl.gov
+
+"""Generates hostinfo.xml at a regular interval."""
+
+__revision__ = '$Revision$'
+
+from os import dup2, execl, fork, uname, wait
+import sys
+import time
+import lxml.etree
+
+import Bcfg2.Options
+
+if __name__ == '__main__':
+ opts = {'repo': Bcfg2.Options.SERVER_REPOSITORY,
+ 'configfile': Bcfg2.Options.CFILE}
+ setup = Bcfg2.Options.OptionParser(opts)
+ setup.parse(sys.argv[1:])
+
+ cfpath = setup['configfile']
+ clientdatapath = "%s/Metadata/clients.xml" % setup['repo']
+
+ clientElement = lxml.etree.parse(clientdatapath)
+ hostlist = [client.get('name') for client in clientElement.findall("Client")]
+
+ pids = {}
+ null = open('/dev/null', 'w+')
+
+ #use uname to detect OS and use -t for darwin and -w for linux
+ #/bin/ping on linux /sbin/ping on os x
+ osname = uname()[0]
+
+
+ while hostlist or pids:
+ if hostlist and len(pids.keys()) < 15:
+ host = hostlist.pop()
+ pid = fork()
+ if pid == 0:
+ # in child
+ dup2(null.fileno(), sys.__stdin__.fileno())
+ dup2(null.fileno(), sys.__stdout__.fileno())
+ dup2(null.fileno(), sys.__stderr__.fileno())
+ if osname == 'Linux':
+ execl('/bin/ping', 'ping', '-w', '5', '-c', '1', host)
+ elif osname in ['Darwin', 'FreeBSD']:
+ execl('/sbin/ping', 'ping', '-t', '5', '-c', '1', host)
+ elif osname == 'SunOS':
+ execl('/usr/sbin/ping', 'ping', host, '56', '1')
+ else: #default
+ execl('/bin/ping', 'ping', '-w', '5', '-c', '1', host)
+ else:
+ pids[pid] = host
+ else:
+ try:
+ (cpid, status) = wait()
+ except OSError:
+ continue
+ chost = pids[cpid]
+ del pids[cpid]
+ elm = clientElement.xpath("//Client[@name='%s']"%chost)[0]
+ if status == 0:
+ elm.set("pingable",'Y')
+ elm.set("pingtime", str(time.time()))
+ else:
+ elm.set("pingable",'N')
+
+ fout = open(clientdatapath, 'w')
+ fout.write(lxml.etree.tostring(clientElement.getroot(), encoding='UTF-8', xml_declaration=True))
+ fout.close()
+
diff --git a/build/scripts-2.7/bcfg2-repo-validate b/build/scripts-2.7/bcfg2-repo-validate
new file mode 100755
index 000000000..4606ab10e
--- /dev/null
+++ b/build/scripts-2.7/bcfg2-repo-validate
@@ -0,0 +1,227 @@
+#!/usr/bin/python
+
+"""
+bcfg2-repo-validate checks all xml files in Bcfg2
+repos against their respective XML schemas.
+"""
+__revision__ = '$Revision$'
+
+import glob
+import lxml.etree
+import os
+import sys
+import Bcfg2.Options
+
+if __name__ == '__main__':
+ opts = {'repo': Bcfg2.Options.SERVER_REPOSITORY,
+ 'prefix': Bcfg2.Options.INSTALL_PREFIX,
+ 'verbose': Bcfg2.Options.VERBOSE,
+ 'configfile': Bcfg2.Options.CFILE}
+ setup = Bcfg2.Options.OptionParser(opts)
+ setup.parse(sys.argv[1:])
+ verbose = setup['verbose']
+ cpath = setup['configfile']
+ prefix = setup['prefix']
+ schemadir = "%s/share/bcfg2/schemas" % (prefix)
+ os.chdir(schemadir)
+ repo = setup['repo']
+
+ # Get a list of all info.xml files in the bcfg2 repository
+ info_list = []
+ for infodir in ['Cfg', 'TGenshi', 'TCheetah']:
+ for root, dirs, files in os.walk('%s/%s' % (repo, infodir)):
+ for filename in files:
+ if filename == 'info.xml':
+ info_list.append(os.path.join(root, filename))
+
+ # get metadata list (with all included files)
+ metadata_list = glob.glob("%s/Metadata/groups.xml" % repo)
+ ref_bundles = set()
+ xdata = lxml.etree.parse("%s/Metadata/groups.xml" % repo)
+ included = set([ent.get('href') for ent in \
+ xdata.findall('./{http://www.w3.org/2001/XInclude}include')])
+ while included:
+ try:
+ filename = included.pop()
+ except KeyError:
+ continue
+ metadata_list.append("%s/Metadata/%s" % (repo, filename))
+ groupdata = lxml.etree.parse("%s/Metadata/%s" % (repo, filename))
+ group_ents = [ent.get('href') for ent in \
+ groupdata.
+ findall('./{http://www.w3.org/2001/XInclude}include')]
+ for ent in group_ents:
+ included.add(ent)
+ included.discard(filename)
+
+ # check for multiple default group definitions
+ default_groups = []
+ for grp in lxml.etree.parse("%s/Metadata/groups.xml" \
+ % repo).findall('.//Group'):
+ if grp.get('default') == 'true':
+ default_groups.append(grp)
+ if len(default_groups) > 1:
+ print("*** Warning: Multiple default groups defined")
+ for grp in default_groups:
+ print(" %s" % grp.get('name'))
+
+ # get all XIncluded bundles
+ xdata.xinclude()
+ for bundle in xdata.findall("//Bundle"):
+ ref_bundles.add("%s/Bundler/%s" % (repo, bundle.get('name')))
+
+ # get lists of all other xml files to validate
+ clients_list = glob.glob("%s/Metadata/clients.xml" % repo)
+ bundle_list = glob.glob("%s/Bundler/*.xml" % repo)
+ genshibundle_list = glob.glob("%s/Bundler/*.genshi" % repo)
+ pkg_list = glob.glob("%s/Pkgmgr/*.xml" % repo)
+ base_list = glob.glob("%s/Base/*.xml" % repo)
+ rules_list = glob.glob("%s/Rules/*.xml" % repo)
+ imageinfo_list = glob.glob("%s/etc/report-configuration.xml" % repo)
+ services_list = glob.glob("%s/Svcmgr/*.xml" % repo)
+ deps_list = glob.glob("%s/Deps/*.xml" % repo)
+ dec_list = glob.glob("%s/Decisions/*" % repo)
+ pkgcfg_list = glob.glob("%s/Packages/config.xml" % repo)
+ gp_list = glob.glob('%s/GroupPatterns/config.xml' % repo)
+
+ # verify attributes for configuration entries
+ # (as defined in doc/server/configurationentries)
+ # TODO: See if it is possible to do this in the schema instead
+ required_configuration_attrs = {
+ 'device': ['name', 'owner', 'group', 'dev_type'],
+ 'directory': ['name', 'owner', 'group', 'perms'],
+ 'file': ['name', 'owner', 'group', 'perms'],
+ 'hardlink': ['name', 'to'],
+ 'symlink': ['name', 'to'],
+ 'ignore': ['name'],
+ 'nonexist': ['name'],
+ 'permissions': ['name', 'owner', 'group', 'perms']}
+ for rfile in rules_list:
+ try:
+ xdata = lxml.etree.parse(rfile)
+ except lxml.etree.XMLSyntaxError, e:
+ print("Failed to parse %s: %s" % (rfile, e))
+ for posixpath in xdata.findall("//Path"):
+ pathname = posixpath.get('name')
+ pathtype = posixpath.get('type')
+ pathset = set(posixpath.attrib.keys())
+ try:
+ required_attrs = set(required_configuration_attrs[pathtype] \
+ + ['type'])
+ except KeyError:
+ continue
+ if 'dev_type' in required_attrs:
+ dev_type = posixpath.get('dev_type')
+ if dev_type in ['block', 'char']:
+ # check if major/minor are specified
+ required_attrs |= set(['major', 'minor'])
+ if pathset.issuperset(required_attrs):
+ continue
+ else:
+ print("The following required attributes are missing for"
+ " Path %s in %s: %s" % (pathname, rfile,
+ [attr for attr in required_attrs.difference(pathset)]))
+
+ # warn on duplicate Pkgmgr entries with the same priority
+ pset = set()
+ for plist in pkg_list:
+ try:
+ xdata = lxml.etree.parse(plist)
+ except lxml.etree.XMLSyntaxError, e:
+ print("Failed to parse %s: %s" % (plist, e))
+ # get priority, type, group
+ priority = xdata.getroot().get('priority')
+ ptype = xdata.getroot().get('type')
+ for pkg in xdata.findall("//Package"):
+ if pkg.getparent().tag == 'Group':
+ grp = pkg.getparent().get('name')
+ if type(grp) is not str and grp.getparent().tag == 'Group':
+ pgrp = grp.getparent().get('name')
+ else:
+ pgrp = 'none'
+ else:
+ grp = 'none'
+ pgrp = 'none'
+ ptuple = (pkg.get('name'), priority, ptype, grp, pgrp)
+ # check if package is already listed with same priority,
+ # type, grp
+ if ptuple in pset:
+ print("Duplicate Package %s, priority:%s, type:%s"\
+ % (pkg.get('name'), priority, ptype))
+ else:
+ pset.add(ptuple)
+
+ filesets = {'metadata': (metadata_list, "%s/metadata.xsd"),
+ 'clients': (clients_list, "%s/clients.xsd"),
+ 'info': (info_list, "%s/info.xsd"),
+ 'bundle': (bundle_list, "%s/bundle.xsd"),
+ 'pkglist': (pkg_list, "%s/pkglist.xsd"),
+ 'base': (base_list, "%s/base.xsd"),
+ 'rules': (rules_list, "%s/rules.xsd"),
+ 'imageinfo': (imageinfo_list, "%s/report-configuration.xsd"),
+ 'services': (services_list, "%s/services.xsd"),
+ 'deps': (deps_list, "%s/deps.xsd"),
+ 'decisions': (dec_list, "%s/decisions.xsd"),
+ 'packages': (pkgcfg_list, "%s/packages.xsd"),
+ 'grouppatterns': (gp_list, "%s/grouppatterns.xsd"),
+ }
+
+ failures = 0
+ for k, (filelist, schemaname) in list(filesets.items()):
+ try:
+ schema = lxml.etree.XMLSchema(lxml.etree.parse(open(schemaname%(schemadir))))
+ except:
+ print("Failed to process schema %s" % (schemaname%(schemadir)))
+ failures = 1
+ continue
+ for filename in filelist:
+ try:
+ datafile = lxml.etree.parse(open(filename))
+ except SyntaxError:
+ print("%s ***FAILS*** to parse \t\t<----" % (filename))
+ os.system("xmllint %s" % filename)
+ failures = 1
+ continue
+ except IOError:
+ print("Failed to open file %s \t\t<---" % (filename))
+ failures = 1
+ continue
+ if schema.validate(datafile):
+ if verbose:
+ print("%s checks out" % (filename))
+ else:
+ rc = os.system("xmllint --noout --xinclude --schema \
+ %s %s > /dev/null 2>/dev/null" % \
+ (schemaname % schemadir, filename))
+ if rc:
+ failures = 1
+ print("%s ***FAILS*** to verify \t\t<----" % (filename))
+ os.system("xmllint --noout --xinclude --schema %s %s" % \
+ (schemaname % schemadir, filename))
+ elif verbose:
+ print("%s checks out" % (filename))
+
+ # print out missing bundle information
+ if verbose:
+ print("")
+ for bundle in ref_bundles:
+ # check for both regular and genshi bundles
+ xmlbundle = "%s.xml" % bundle
+ genshibundle = "%s.genshi" % bundle
+ allbundles = bundle_list + genshibundle_list
+ if xmlbundle not in allbundles and \
+ genshibundle not in allbundles:
+ print("*** Warning: Bundle %s referenced, but does not "
+ "exist." % bundle)
+ # verify bundle name attribute matches filename
+ for bundle in (bundle_list + genshibundle_list):
+ fname = bundle.split('Bundler/')[1].split('.')[0]
+ xdata = lxml.etree.parse(bundle)
+ bname = xdata.getroot().get('name')
+ if fname != bname:
+ print("The following names are inconsistent:")
+ print(" Filename is %s" % fname)
+ print(" Bundle name found in %s is %s" % (fname, bname))
+
+
+ raise SystemExit, failures
diff --git a/build/scripts-2.7/bcfg2-reports b/build/scripts-2.7/bcfg2-reports
new file mode 100755
index 000000000..85338b0d8
--- /dev/null
+++ b/build/scripts-2.7/bcfg2-reports
@@ -0,0 +1,282 @@
+#!/usr/bin/python
+"""Query reporting system for client status."""
+__revision__ = '$Revision$'
+
+import os
+import sys
+
+import Bcfg2.Server.Reports.settings
+
+project_directory = os.path.dirname(Bcfg2.Server.Reports.settings.__file__)
+project_name = os.path.basename(project_directory)
+sys.path.append(os.path.join(project_directory, '..'))
+project_module = __import__(project_name, '', '', [''])
+sys.path.pop()
+# Set DJANGO_SETTINGS_MODULE appropriately.
+os.environ['DJANGO_SETTINGS_MODULE'] = '%s.settings' % project_name
+
+from Bcfg2.Server.Reports.reports.models import Client
+from getopt import getopt
+import datetime
+import fileinput
+
+def timecompare(client1, client2):
+ """Compares two clients by their timestamps."""
+ return cmp(client1.current_interaction.timestamp, \
+ client2.current_interaction.timestamp)
+
+def namecompare(client1, client2):
+ """Compares two clients by their names."""
+ return cmp(client1.name, client2.name)
+
+def statecompare(client1, client2):
+ """Compares two clients by their states."""
+ clean1 = client1.current_interaction.isclean()
+ clean2 = client2.current_interaction.isclean()
+
+ if clean1 and not clean2:
+ return -1
+ elif clean2 and not clean1:
+ return 1
+ else:
+ return 0
+
+def crit_compare(criterion, client1, client2):
+ """Compares two clients by the criteria provided in criterion."""
+ for crit in criterion:
+ comp = 0
+ if crit == 'name':
+ comp = namecompare(client1, client2)
+ elif crit == 'state':
+ comp = statecompare(client1, client2)
+ elif crit == 'time':
+ comp = timecompare(client1, client2)
+
+ if comp != 0:
+ return comp
+
+ return 0
+
+def print_fields(fields, cli, max_name, entrydict):
+ """
+ Prints the fields specified in fields of cli, max_name
+ specifies the column width of the name column.
+ """
+ fmt = ''
+ for field in fields:
+ if field == 'name':
+ fmt += ("%%-%ds " % (max_name))
+ else:
+ fmt += "%s "
+ fdata = []
+ for field in fields:
+ if field == 'time':
+ fdata.append(str(cli.current_interaction.timestamp))
+ elif field == 'state':
+ if cli.current_interaction.isclean():
+ fdata.append("clean")
+ else:
+ fdata.append("dirty")
+ else:
+ try:
+ fdata.append(getattr(cli, field))
+ except:
+ fdata.append("N/A")
+
+ display = fmt % tuple(fdata)
+ if len(entrydict) > 0:
+ display += " "
+ display += str(entrydict[cli])
+ print display
+
+def print_entry(item, max_name):
+ fmt = ("%%-%ds " % (max_name))
+ fdata = item.entry.kind + ":" + item.entry.name
+ display = fmt % (fdata)
+ print display
+
+fields = ""
+sort = ""
+badentry = ""
+extraentry = ""
+expire = ""
+singlehost = ""
+
+c_list = Client.objects.all()
+
+result = list()
+entrydict = dict()
+
+args = sys.argv[1:]
+opts, pargs = getopt(args, 'ab:cde:hs:x:',
+ ['stale', 'sort=', 'fields=', 'badentry=', 'extraentry='])
+
+for option in opts:
+ if len(option) > 0:
+ if option[0] == '--fields':
+ fields = option[1]
+ if option[0] == '--sort':
+ sort = option[1]
+ if option[0] == '--badentry':
+ badentry = option[1]
+ if option[0] == '--extraentry':
+ extraentry = option[1]
+ if option[0] == '-x':
+ expire = option[1]
+ if option[0] == '-s' or option[0] == '-b' or option[0] == '-e':
+ singlehost = option[1]
+
+if expire != "":
+ for c_inst in c_list:
+ if expire == c_inst.name:
+ if c_inst.expiration == None:
+ c_inst.expiration = datetime.datetime.now()
+ print "Host expired."
+ else:
+ c_inst.expiration = None
+ print "Host un-expired."
+ c_inst.save()
+
+elif '-h' in args:
+ print """Usage: python bcfg2-reports [option] ...
+
+Options and arguments (and corresponding environment variables):
+-a : shows all hosts, including expired hosts
+-b NAME : single-host mode - shows bad entries from the
+ current interaction of NAME
+-c : shows only clean hosts
+-d : shows only dirty hosts
+-e NAME : single-host mode - shows extra entries from the
+ current interaction of NAME
+-h : shows help and usage info about bcfg2-reports
+-s NAME : single-host mode - shows bad and extra entries from
+ the current interaction of NAME
+-x NAME : toggles expired/unexpired state of NAME
+--badentry=KIND,NAME : shows only hosts whose current interaction has bad
+ entries in of KIND kind and NAME name; if a single
+ argument ARG1 is given, then KIND,NAME pairs will be
+ read from a file of name ARG1
+--extraentry=KIND,NAME : shows only hosts whose current interaction has extra
+ entries in of KIND kind and NAME name; if a single
+ argument ARG1 is given, then KIND,NAME pairs will be
+ read from a file of name ARG1
+--fields=ARG1,ARG2,... : only displays the fields ARG1,ARG2,...
+ (name,time,state)
+--sort=ARG1,ARG2,... : sorts output on ARG1,ARG2,... (name,time,state)
+--stale : shows hosts which haven't run in the last 24 hours
+"""
+elif singlehost != "":
+ for c_inst in c_list:
+ if singlehost == c_inst.name:
+ baditems = c_inst.current_interaction.bad()
+ if len(baditems) > 0 and ('-b' in args or '-s' in args):
+ print "Bad Entries:"
+ max_name = -1
+ for item in baditems:
+ if len(item.entry.name) > max_name:
+ max_name = len(item.entry.name)
+ for item in baditems:
+ print_entry(item, max_name)
+ extraitems = c_inst.current_interaction.extra()
+ if len(extraitems) > 0 and ('-e' in args or '-s' in args):
+ print "Extra Entries:"
+ max_name = -1
+ for item in extraitems:
+ if len(item.entry.name) > max_name:
+ max_name = len(item.entry.name)
+ for item in extraitems:
+ print_entry(item, max_name)
+
+
+else:
+ if fields == "":
+ fields = ['name', 'time', 'state']
+ else:
+ fields = fields.split(',')
+
+ if sort != "":
+ sort = sort.split(',')
+
+ if badentry != "":
+ badentry = badentry.split(',')
+
+ if extraentry != "":
+ extraentry = extraentry.split(',')
+
+ # stale hosts
+ if '--stale' in args:
+ for c_inst in c_list:
+ if c_inst.current_interaction.isstale():
+ result.append(c_inst)
+ # clean hosts
+ elif '-c' in args:
+ for c_inst in c_list:
+ if c_inst.current_interaction.isclean():
+ result.append(c_inst)
+ # dirty hosts
+ elif '-d' in args:
+ for c_inst in c_list:
+ if not c_inst.current_interaction.isclean():
+ result.append(c_inst)
+
+ elif badentry != "":
+ if len(badentry) == 1:
+ fileread = fileinput.input(badentry[0])
+ for line in fileread:
+ badentry = line.strip().split(',')
+ for c_inst in c_list:
+ baditems = c_inst.current_interaction.bad()
+ for item in baditems:
+ if item.name == badentry[1] and item.kind == badentry[0]:
+ result.append(c_inst)
+ if c_inst in entrydict:
+ entrydict.get(c_inst).append(badentry[1])
+ else:
+ entrydict[c_inst] = [badentry[1]]
+ break
+ else:
+ for c_inst in c_list:
+ baditems = c_inst.current_interaction.bad()
+ for item in baditems:
+ if item.name == badentry[1] and item.kind == badentry[0]:
+ result.append(c_inst)
+ break
+ elif extraentry != "":
+ if len(extraentry) == 1:
+ fileread = fileinput.input(extraentry[0])
+ for line in fileread:
+ extraentry = line.strip().split(',')
+ for c_inst in c_list:
+ extraitems = c_inst.current_interaction.extra()
+ for item in extraitems:
+ if item.name == extraentry[1] and item.kind == extraentry[0]:
+ result.append(c_inst)
+ if c_inst in entrydict:
+ entrydict.get(c_inst).append(extraentry[1])
+ else:
+ entrydict[c_inst] = [extraentry[1]]
+ break
+ else:
+ for c_inst in c_list:
+ extraitems = c_inst.current_interaction.extra()
+ for item in extraitems:
+ if item.name == extraentry[1] and item.kind == extraentry[0]:
+ result.append(c_inst)
+ break
+
+ else:
+ for c_inst in c_list:
+ result.append(c_inst)
+ max_name = -1
+ if 'name' in fields:
+ for c_inst in result:
+ if len(c_inst.name) > max_name:
+ max_name = len(c_inst.name)
+
+ if sort != "":
+ result.sort(lambda x, y: crit_compare(sort, x, y))
+
+ if fields != "":
+ for c_inst in result:
+ if '-a' in args or c_inst.expiration == None:
+ print_fields(fields, c_inst, max_name, entrydict)
diff --git a/build/scripts-2.7/bcfg2-server b/build/scripts-2.7/bcfg2-server
new file mode 100755
index 000000000..115708bf9
--- /dev/null
+++ b/build/scripts-2.7/bcfg2-server
@@ -0,0 +1,79 @@
+#!/usr/bin/python
+
+"""The XML-RPC Bcfg2 server."""
+__revision__ = '$Revision$'
+
+import logging
+import os.path
+import sys
+
+import Bcfg2.Logger
+import Bcfg2.Options
+import Bcfg2.Component
+import Bcfg2.Server.Plugins.Metadata
+from Bcfg2.Server.Core import CoreInitError
+
+logger = logging.getLogger('bcfg2-server')
+
+if __name__ == '__main__':
+
+ OPTINFO = {
+ 'configfile': Bcfg2.Options.CFILE,
+ 'daemon' : Bcfg2.Options.DAEMON,
+ 'debug' : Bcfg2.Options.DEBUG,
+ 'help' : Bcfg2.Options.HELP,
+ 'verbose' : Bcfg2.Options.VERBOSE,
+ 'to_file' : Bcfg2.Options.LOGGING_FILE_PATH,
+ }
+
+ OPTINFO.update({'repo' : Bcfg2.Options.SERVER_REPOSITORY,
+ 'plugins' : Bcfg2.Options.SERVER_PLUGINS,
+ 'password' : Bcfg2.Options.SERVER_PASSWORD,
+ 'fm' : Bcfg2.Options.SERVER_FILEMONITOR,
+ })
+
+ OPTINFO.update({'key' : Bcfg2.Options.SERVER_KEY,
+ 'cert' : Bcfg2.Options.SERVER_CERT,
+ 'ca' : Bcfg2.Options.SERVER_CA,
+ 'location' : Bcfg2.Options.SERVER_LOCATION,
+ 'passwd' : Bcfg2.Options.SERVER_PASSWORD,
+ 'static' : Bcfg2.Options.SERVER_STATIC,
+ 'encoding' : Bcfg2.Options.ENCODING,
+ 'filelog' : Bcfg2.Options.LOGGING_FILE_PATH,
+ 'protocol' : Bcfg2.Options.SERVER_PROTOCOL,
+ })
+
+ setup = Bcfg2.Options.OptionParser(OPTINFO)
+ setup.parse(sys.argv[1:])
+ try:
+ # check whether the specified bcfg2.conf exists
+ if not os.path.exists(setup['configfile']):
+ print("Could not read %s" % setup['configfile'])
+ sys.exit(1)
+ Bcfg2.Component.run_component(Bcfg2.Server.Core.Core,
+ location=setup['location'],
+ daemon = setup['daemon'],
+ pidfile_name = setup['daemon'],
+ protocol = setup['protocol'],
+ to_file=setup['to_file'],
+ cfile=setup['configfile'],
+ register=False,
+ cls_kwargs={'repo':setup['repo'],
+ 'plugins':setup['plugins'],
+ 'password':setup['password'],
+ 'encoding':setup['encoding'],
+ 'ca':setup['ca'],
+ 'filemonitor':setup['fm'],
+ 'start_fam_thread':True},
+ keyfile=setup['key'],
+ certfile=setup['cert'],
+ ca=setup['ca'],
+ )
+ except CoreInitError, msg:
+ logger.error(msg)
+ logger.error("exiting")
+ sys.exit(1)
+ except KeyboardInterrupt:
+ sys.exit(1)
+ sys.exit(0)
+