diff options
Diffstat (limited to 'src/sbin')
-rwxr-xr-x | src/sbin/bcfg2 | 3 | ||||
-rwxr-xr-x | src/sbin/bcfg2-admin | 18 | ||||
-rwxr-xr-x | src/sbin/bcfg2-build-reports | 2 | ||||
-rwxr-xr-x | src/sbin/bcfg2-info | 276 | ||||
-rwxr-xr-x | src/sbin/bcfg2-lint | 1 | ||||
-rwxr-xr-x | src/sbin/bcfg2-ping-sweep | 2 | ||||
-rwxr-xr-x | src/sbin/bcfg2-reports | 1 | ||||
-rwxr-xr-x | src/sbin/bcfg2-server | 1 | ||||
-rwxr-xr-x[-rw-r--r--] | src/sbin/bcfg2-test | 107 | ||||
-rwxr-xr-x | src/sbin/bcfg2-yum-helper | 118 |
10 files changed, 378 insertions, 151 deletions
diff --git a/src/sbin/bcfg2 b/src/sbin/bcfg2 index 58f2964f9..fb34e627b 100755 --- a/src/sbin/bcfg2 +++ b/src/sbin/bcfg2 @@ -1,7 +1,6 @@ #!/usr/bin/env python """Bcfg2 Client""" -__revision__ = '$Revision$' import fcntl import logging @@ -109,7 +108,7 @@ class Client: 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") + self.logger.error("Service removal is nonsensical; removed services will only be disabled") if self.setup['remove'] not in [False, 'all', 'Services', diff --git a/src/sbin/bcfg2-admin b/src/sbin/bcfg2-admin index 09117a3f4..d3b06733f 100755 --- a/src/sbin/bcfg2-admin +++ b/src/sbin/bcfg2-admin @@ -6,13 +6,12 @@ import logging import Bcfg2.Server.Core import Bcfg2.Logger import Bcfg2.Options +import Bcfg2.Server.Admin # Compatibility import from Bcfg2.Bcfg2Py3k import StringIO log = logging.getLogger('bcfg2-admin') -import Bcfg2.Server.Admin - def mode_import(modename): """Load Bcfg2.Server.Admin.<mode>.""" modname = modename.capitalize() @@ -42,8 +41,16 @@ def main(): 'configfile': Bcfg2.Options.CFILE, 'help': Bcfg2.Options.HELP, 'verbose': Bcfg2.Options.VERBOSE, + 'repo': Bcfg2.Options.SERVER_REPOSITORY, + 'plugins': Bcfg2.Options.SERVER_PLUGINS, + 'event debug': Bcfg2.Options.DEBUG, + 'filemonitor': Bcfg2.Options.SERVER_FILEMONITOR, + 'password': Bcfg2.Options.SERVER_PASSWORD, + 'encoding': Bcfg2.Options.ENCODING, } setup = Bcfg2.Options.OptionParser(optinfo) + # override default help message to include description of all modes + setup.hm = "%s\n%s" % (setup.buildHelpMessage(), create_description()) setup.parse(sys.argv[1:]) log_args = dict(to_syslog=False, to_console=logging.WARNING) @@ -58,13 +65,12 @@ def main(): setup['args'] = [setup['args'][1], setup['args'][0]] else: # Print short help for all modes - print("Usage:\n %s" % setup.buildHelpMessage()) - print(create_description()) + print(setup.hm) raise SystemExit(0) if setup['args'][0] in get_modes(): modname = setup['args'][0].capitalize() - if len(setup['args']) > 1 and setup['args'][1] == 'help': + if len(setup['args']) > 1 and setup['args'][1] == 'help': print(mode_import(modname).__longhelp__) raise SystemExit(0) try: @@ -73,7 +79,7 @@ def main(): e = sys.exc_info()[1] log.error("Failed to load admin mode %s: %s" % (modname, e)) raise SystemExit(1) - mode = mode_cls(setup['configfile']) + mode = mode_cls(setup) mode(setup['args'][1:]) if hasattr(mode, 'bcore'): mode.bcore.shutdown() diff --git a/src/sbin/bcfg2-build-reports b/src/sbin/bcfg2-build-reports index 7122fb300..7fa08110a 100755 --- a/src/sbin/bcfg2-build-reports +++ b/src/sbin/bcfg2-build-reports @@ -4,8 +4,6 @@ bcfg2-build-reports generates & distributes reports of statistic information for Bcfg2.""" -__revision__ = '$Revision$' - import copy import getopt import re diff --git a/src/sbin/bcfg2-info b/src/sbin/bcfg2-info index 4412c712a..4fe4ce9d5 100755 --- a/src/sbin/bcfg2-info +++ b/src/sbin/bcfg2-info @@ -1,20 +1,22 @@ #!/usr/bin/env python - """This tool loads the Bcfg2 core into an interactive debugger.""" -__revision__ = '$Revision$' -from code import InteractiveConsole +import os +import sys import cmd import errno import getopt +import fnmatch import logging -import lxml.etree -import os -import sys import tempfile +import lxml.etree +from code import InteractiveConsole try: - import profile + try: + import cProfile as profile + except: + import profile import pstats have_profile = True except: @@ -31,7 +33,8 @@ logger = logging.getLogger('bcfg2-info') USAGE = """Commands: build <hostname> <filename> - Build config for hostname, writing to filename builddir <hostname> <dirname> - Build config for hostname, writing separate files to dirname -buildall <directory> - Build configs for all clients in directory +buildall <directory> [<hostnames*>] - Build configs for all clients in directory +buildallfile <directory> <filename> [<hostnames*>] - Build config file for all clients in directory buildfile <filename> <hostname> - Build config file for hostname (not written to disk) buildbundle <bundle> <hostname> - Render a templated bundle for hostname (not written to disk) bundles - Print out group/bundle information @@ -42,12 +45,13 @@ event_debug - Display filesystem events as they are processed groups - List groups help - Print this list of available commands mappings <type*> <name*> - Print generator mappings for optional type and name +packageresolve <hostname> <package> [<package>...] - Resolve the specified set of packages +packagesources <hostname> - Show package sources profile <command> <args> - Profile a single bcfg2-info command quit - Exit the bcfg2-info command line showentries <hostname> <type> - Show abstract configuration entries for a given host showclient <client1> <client2> - Show metadata for given hosts -update - Process pending file events -version - Print version of this tool""" +update - Process pending file events""" BUILDDIR_USAGE = """Usage: builddir [-f] <hostname> <output dir> @@ -73,10 +77,12 @@ class mockLog(object): def debug(self, *args, **kwargs): pass + class dummyError(Exception): """This is just a dummy.""" pass + class FileNotBuilt(Exception): """Thrown when File entry contains no content.""" def __init__(self, value): @@ -85,6 +91,30 @@ class FileNotBuilt(Exception): def __str__(self): return repr(self.value) + +def getClientList(hostglobs): + """ given a host glob, get a list of clients that match it """ + # special cases to speed things up: + if '*' in hostglobs: + return list(self.metadata.clients.keys()) + has_wildcards = False + for glob in hostglobs: + # check if any wildcard characters are in the string + if set('*?[]') & set(glob): + has_wildcards = True + break + if not has_wildcards: + return hostglobs + + rv = set() + clist = set(self.metadata.clients.keys()) + for glob in hostglobs: + for client in clist: + if fnmatch.fnmatch(client, glob): + rv.update(client) + clist.difference_update(rv) + return list(rv) + def printTabular(rows): """Print data in tabular format.""" cmax = tuple([max([len(str(row[index])) for row in rows]) + 1 \ @@ -103,11 +133,11 @@ def displayTrace(trace, num=80, sort=('time', 'calls')): class infoCore(cmd.Cmd, Bcfg2.Server.Core.Core): """Main class for bcfg2-info.""" - def __init__(self, repo, plgs, passwd, encoding, event_debug): + def __init__(self, repo, plgs, passwd, encoding, event_debug, filemonitor='default'): cmd.Cmd.__init__(self) try: Bcfg2.Server.Core.Core.__init__(self, repo, plgs, passwd, - encoding) + encoding, filemonitor=filemonitor) if event_debug: self.fam.debug = True except Bcfg2.Server.Core.CoreInitError: @@ -162,8 +192,13 @@ class infoCore(cmd.Cmd, Bcfg2.Server.Core.Core): print("Dropping to python interpreter; press ^D to resume") try: import IPython - shell = IPython.Shell.IPShell(argv=[], user_ns=locals()) - shell.mainloop() + if hasattr(IPython, "Shell"): + shell = IPython.Shell.IPShell(argv=[], user_ns=locals()) + shell.mainloop() + elif hasattr(IPython, "embed"): + IPython.embed(user_ns=locals()) + else: + raise ImportError except ImportError: sh.interact() @@ -187,10 +222,6 @@ class infoCore(cmd.Cmd, Bcfg2.Server.Core.Core): """Process pending filesystem 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() @@ -204,12 +235,9 @@ class infoCore(cmd.Cmd, Bcfg2.Server.Core.Core): 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), + lxml.etree.ElementTree(self.BuildConfiguration(client)).write(ofile, encoding='UTF-8', xml_declaration=True, pretty_print=True) - output.write(data) - output.close() else: print('Usage: build [-f] <hostname> <output file>') @@ -250,42 +278,99 @@ class infoCore(cmd.Cmd, Bcfg2.Server.Core.Core): self.help_builddir() def do_buildall(self, args): - if len(args.split()) != 1: - print("Usage: buildall <directory>") + if len(args.split()) < 1: + print("Usage: buildall <directory> [<hostnames*>]") return + + destdir = args[0] try: - os.mkdir(args) - except: - pass - for client in self.metadata.clients: + os.mkdir(destdir) + except OSError: + err = sys.exc_info()[1] + if err.errno != 17: + print("Could not create %s: %s" % (destdir, err)) + if len(args) > 1: + clients = getClientList(args[1:]) + else: + clients = list(self.metadata.clients.keys()) + for client in clients: self.do_build("%s %s/%s.xml" % (client, args, client)) + def do_buildallfile(self, args): + """Build a config file for all clients.""" + usage = 'Usage: buildallfile [--altsrc=<altsrc>] <directory> <filename> [<hostnames*>]' + try: + opts, args = getopt.gnu_getopt(args.split(), '', ['altsrc=']) + except: + print(usage) + return + altsrc = None + for opt in opts: + if opt[0] == '--altsrc': + altsrc = opt[1] + if len(args) < 2: + print(usage) + return + + destdir = args[0] + filename = args[1] + try: + os.mkdir(destdir) + except OSError: + err = sys.exc_info()[1] + if err.errno != 17: + print("Could not create %s: %s" % (destdir, err)) + if len(args) > 2: + clients = getClientList(args[1:]) + else: + clients = list(self.metadata.clients.keys()) + if altsrc: + args = "--altsrc %s -f %%s %%s %%s" % altsrc + else: + args = "-f %s %s %s" + for client in clients: + self.do_buildfile(args % (os.path.join(destdir, client), + filename, client)) + def do_buildfile(self, args): """Build a config file for client.""" - usage = 'Usage: buildfile [--altsrc=<altsrc>] filename hostname' + usage = 'Usage: buildfile [-f <outfile>] [--altsrc=<altsrc>] filename hostname' try: - opts, alist = getopt.gnu_getopt(args.split(), '', ['altsrc=']) + opts, alist = getopt.gnu_getopt(args.split(), 'f:', ['altsrc=']) except: print(usage) return altsrc = None + outfile = None for opt in opts: if opt[0] == '--altsrc': altsrc = opt[1] - if len(alist) == 2: - fname, client = alist - entry = lxml.etree.Element('Path', type='file', name=fname) - if altsrc: - entry.set("altsrc", altsrc) - try: - metadata = self.build_metadata(client) - self.Bind(entry, metadata) - print(lxml.etree.tostring(entry, encoding="UTF-8", - xml_declaration=True)) - except: - print("Failed to build entry %s for host %s" % (fname, client)) - else: + elif opt[0] == '-f': + outfile = opt[1] + if len(alist) != 2: print(usage) + return + + fname, client = alist + entry = lxml.etree.Element('Path', type='file', name=fname) + if altsrc: + entry.set("altsrc", altsrc) + try: + metadata = self.build_metadata(client) + self.Bind(entry, metadata) + data = lxml.etree.tostring(entry, encoding="UTF-8", + xml_declaration=True) + if outfile: + open(outfile, 'w').write(data) + else: + print(data) + except IOError: + err = sys.exc_info()[1] + print("Could not write to %s: %s" % (outfile, err)) + print(data) + except Exception: + print("Failed to build entry %s for host %s" % (fname, client)) + raise def do_buildbundle(self, args): """Render a bundle for client.""" @@ -454,7 +539,7 @@ class infoCore(cmd.Cmd, Bcfg2.Server.Core.Core): try: meta = self.build_metadata(args) except Bcfg2.Server.Plugins.Metadata.MetadataConsistencyError: - print("Unable to find metadata for host %s" % client) + print("Unable to find metadata for host %s" % args) return structures = self.GetStructures(meta) for clist in [struct.findall('Path') for struct in structures]: @@ -473,6 +558,60 @@ class infoCore(cmd.Cmd, Bcfg2.Server.Core.Core): continue print(cand[0].name) + def do_packageresolve(self, args): + arglist = args.split(" ") + if len(arglist) < 2: + print("Usage: packageresolve <hostname> <package> [<package>...]") + return + + hostname = arglist[0] + initial = arglist[1:] + metadata = self.build_metadata(hostname) + self.plugins['Packages'].toggle_debug() + collection = self.plugins['Packages']._get_collection(metadata) + packages, unknown = collection.complete(initial) + newpkgs = list(packages.difference(initial)) + print("%d initial packages" % len(initial)) + print(" %s" % "\n ".join(initial)) + print("%d new packages added" % len(newpkgs)) + if newpkgs: + print(" %s" % "\n ".join(newpkgs)) + print("%d unknown packages" % len(unknown)) + if unknown: + print(" %s" % "\n ".join(unknown)) + + def do_packagesources(self, args): + try: + metadata = self.build_metadata(args) + except Bcfg2.Server.Plugins.Metadata.MetadataConsistencyError: + print("Unable to build metadata for host %s" % args) + return + collection = self.plugins['Packages']._get_collection(metadata) + for source in collection.sources: + # get_urls() loads url_map as a side-effect + source.get_urls() + for url_map in source.url_map: + for arch in url_map['arches']: + # make sure client is in all the proper arch groups + if arch not in metadata.groups: + continue + reponame = source.get_repo_name(url_map) + print("Name: %s" % reponame) + print(" Type: %s" % source.ptype) + if url_map['url'] != '': + print(" URL: %s" % url_map['url']) + elif url_map['rawurl'] != '': + print(" RAWURL: %s" % url_map['rawurl']) + if source.gpgkeys: + print(" GPG Key(s): %s" % ", ".join(source.gpgkeys)) + else: + print(" GPG Key(s): None") + if len(source.blacklist): + print(" Blacklist: %s" % ", ".join(source.blacklist)) + if len(source.whitelist): + print(" Whitelist: %s" % ", ".join(source.whitelist)) + print("") + def do_profile(self, arg): """.""" if not have_profile: @@ -496,42 +635,43 @@ if __name__ == '__main__': optinfo = { 'configfile': Bcfg2.Options.CFILE, 'help': Bcfg2.Options.HELP, - } - optinfo.update({ - 'event debug': Bcfg2.Options.DEBUG, - 'profile': Bcfg2.Options.CORE_PROFILE, - 'encoding': Bcfg2.Options.ENCODING, - # Server options - 'repo': Bcfg2.Options.SERVER_REPOSITORY, - 'plugins': Bcfg2.Options.SERVER_PLUGINS, - 'password': Bcfg2.Options.SERVER_PASSWORD, - 'mconnect': Bcfg2.Options.SERVER_MCONNECT, - 'filemonitor': Bcfg2.Options.SERVER_FILEMONITOR, - 'location': Bcfg2.Options.SERVER_LOCATION, - 'static': Bcfg2.Options.SERVER_STATIC, - 'key': Bcfg2.Options.SERVER_KEY, - 'cert': Bcfg2.Options.SERVER_CERT, - 'ca': Bcfg2.Options.SERVER_CA, - 'password': Bcfg2.Options.SERVER_PASSWORD, - 'protocol': Bcfg2.Options.SERVER_PROTOCOL, - # More options - 'logging': Bcfg2.Options.LOGGING_FILE_PATH - }) + 'event debug': Bcfg2.Options.DEBUG, + 'profile': Bcfg2.Options.CORE_PROFILE, + 'encoding': Bcfg2.Options.ENCODING, + # Server options + 'repo': Bcfg2.Options.SERVER_REPOSITORY, + 'plugins': Bcfg2.Options.SERVER_PLUGINS, + 'password': Bcfg2.Options.SERVER_PASSWORD, + 'mconnect': Bcfg2.Options.SERVER_MCONNECT, + 'filemonitor': Bcfg2.Options.SERVER_FILEMONITOR, + 'location': Bcfg2.Options.SERVER_LOCATION, + 'static': Bcfg2.Options.SERVER_STATIC, + 'key': Bcfg2.Options.SERVER_KEY, + 'cert': Bcfg2.Options.SERVER_CERT, + 'ca': Bcfg2.Options.SERVER_CA, + 'password': Bcfg2.Options.SERVER_PASSWORD, + 'protocol': Bcfg2.Options.SERVER_PROTOCOL, + # More options + 'logging': Bcfg2.Options.LOGGING_FILE_PATH + } setup = Bcfg2.Options.OptionParser(optinfo) + setup.hm = "Usage:\n %s\n%s" % (setup.buildHelpMessage(), + USAGE) + setup.parse(sys.argv[1:]) if setup['args'] and setup['args'][0] == 'help': - print(USAGE) + print(setup.hm) sys.exit(0) elif setup['profile'] and have_profile: prof = profile.Profile() loop = prof.runcall(infoCore, setup['repo'], setup['plugins'], setup['password'], setup['encoding'], - setup['event debug']) + setup['event debug'], setup['filemonitor']) displayTrace(prof) else: if setup['profile']: print("Profiling functionality not available.") loop = infoCore(setup['repo'], setup['plugins'], setup['password'], - setup['encoding'], setup['event debug']) + setup['encoding'], setup['event debug'], setup['filemonitor']) loop.Run(setup['args']) diff --git a/src/sbin/bcfg2-lint b/src/sbin/bcfg2-lint index 2d371f4aa..ad6b6139c 100755 --- a/src/sbin/bcfg2-lint +++ b/src/sbin/bcfg2-lint @@ -1,7 +1,6 @@ #!/usr/bin/env python """This tool examines your Bcfg2 specifications for errors.""" -__revision__ = '$Revision$' import sys import inspect diff --git a/src/sbin/bcfg2-ping-sweep b/src/sbin/bcfg2-ping-sweep index 70f718690..be8994be3 100755 --- a/src/sbin/bcfg2-ping-sweep +++ b/src/sbin/bcfg2-ping-sweep @@ -3,8 +3,6 @@ """Generates hostinfo.xml at a regular interval.""" -__revision__ = '$Revision$' - from os import dup2, execl, fork, uname, wait import sys import time diff --git a/src/sbin/bcfg2-reports b/src/sbin/bcfg2-reports index 6acdd27e3..1f101b9a7 100755 --- a/src/sbin/bcfg2-reports +++ b/src/sbin/bcfg2-reports @@ -1,6 +1,5 @@ #!/usr/bin/env python """Query reporting system for client status.""" -__revision__ = '$Revision$' import os import sys diff --git a/src/sbin/bcfg2-server b/src/sbin/bcfg2-server index 546d5a249..89bc7c331 100755 --- a/src/sbin/bcfg2-server +++ b/src/sbin/bcfg2-server @@ -1,7 +1,6 @@ #!/usr/bin/env python """The XML-RPC Bcfg2 server.""" -__revision__ = '$Revision$' import logging import os.path diff --git a/src/sbin/bcfg2-test b/src/sbin/bcfg2-test index a0257ca52..01a2a4893 100644..100755 --- a/src/sbin/bcfg2-test +++ b/src/sbin/bcfg2-test @@ -1,41 +1,60 @@ #!/usr/bin/env python -"""This tool verifies that all clients known to the server build without failures""" +"""This tool verifies that all clients known to the server build +without failures""" import sys +import fnmatch +import logging +import Bcfg2.Logger import Bcfg2.Server.Core from nose.core import TestProgram +from nose.suite import LazySuite from unittest import TestCase class ClientTest(TestCase): """ - A test case representing the build of all of the configuration for a single host. - Checks that none of the build config entities has had a failure when it is building. - Optionally ignores some config files that we know will cause errors (because they - are private files we don't have access to, for instance) + A test case representing the build of all of the configuration for + a single host. Checks that none of the build config entities has + had a failure when it is building. Optionally ignores some config + files that we know will cause errors (because they are private + files we don't have access to, for instance) """ __test__ = False # Do not collect - def __init__(self, bcfg2_core, client): + def __init__(self, bcfg2_core, client, ignore=None): TestCase.__init__(self) self.bcfg2_core = bcfg2_core self.client = client + if ignore is None: + self.ignore = dict() + else: + self.ignore = ignore + + def ignore_entry(self, tag, name): + if tag in self.ignore: + if name in self.ignore[tag]: + return True + else: + # try wildcard matching + for pattern in self.ignore[tag]: + if fnmatch.fnmatch(name, pattern): + return True + return False def runTest(self): config = self.bcfg2_core.BuildConfiguration(self.client) - failures = config.xpath('//*[@failure]') - def format_failure(failure): - return "%s(%s): %s" % ( - failure.tag, - failure.attrib.get('name'), - failure.attrib.get('failure') - ) + failures = [] + msg = ["Failures:"] + for failure in config.xpath('//*[@failure]'): + if not self.ignore_entry(failure.tag, failure.get('name')): + failures.append(failure) + msg.append("%s:%s: %s" % (failure.tag, failure.get("name"), + failure.get("failure"))) + + assert len(failures) == 0, "\n".join(msg) - assert len(failures) == 0, "Failures:\n%s" % "\n".join( - [format_failure(failure) for failure in failures] - ) - def __str__(self): return "ClientTest(%s)" % self.client @@ -43,25 +62,55 @@ class ClientTest(TestCase): def main(): optinfo = { - 'configfile': Bcfg2.Options.CFILE, - 'help': Bcfg2.Options.HELP, - 'encoding': Bcfg2.Options.ENCODING, - 'repo': Bcfg2.Options.SERVER_REPOSITORY, - 'plugins': Bcfg2.Options.SERVER_PLUGINS, - 'password': Bcfg2.Options.SERVER_PASSWORD, - } + 'configfile': Bcfg2.Options.CFILE, + 'help': Bcfg2.Options.HELP, + 'encoding': Bcfg2.Options.ENCODING, + 'repo': Bcfg2.Options.SERVER_REPOSITORY, + 'plugins': Bcfg2.Options.SERVER_PLUGINS, + 'password': Bcfg2.Options.SERVER_PASSWORD, + 'verbose': Bcfg2.Options.VERBOSE, + 'noseopts': Bcfg2.Options.TEST_NOSEOPTS, + 'ignore': Bcfg2.Options.TEST_IGNORE, + } setup = Bcfg2.Options.OptionParser(optinfo) + setup.hm = \ + "bcfg2-test [options] [client] [client] [...]\nOptions:\n %s" % \ + setup.buildHelpMessage() setup.parse(sys.argv[1:]) + + if setup['verbose']: + Bcfg2.Logger.setup_logging("bcfg2-test", to_syslog=False) + core = Bcfg2.Server.Core.Core( setup['repo'], setup['plugins'], setup['password'], - setup['encoding'] - ) - core.fam.handle_events_in_interval(4) - suite = [ClientTest(core, client) for client in core.metadata.clients] + setup['encoding'], + filemonitor='pseudo' + ) + + ignore = dict() + for entry in setup['ignore']: + tag, name = entry.split(":") + try: + ignore[tag].append(name) + except KeyError: + ignore[tag] = [name] + + def run_tests(): + core.fam.handle_events_in_interval(0.1) + + if setup['args']: + clients = setup['args'] + else: + clients = core.metadata.clients + + for client in clients: + logging.info("Building %s" % client) + yield ClientTest(core, client, ignore) - TestProgram(argv=sys.argv[0:1], suite = suite) + TestProgram(argv=sys.argv[0:1] + setup['noseopts'], + suite=LazySuite(run_tests)) if __name__ == "__main__": sys.exit(main()) diff --git a/src/sbin/bcfg2-yum-helper b/src/sbin/bcfg2-yum-helper index 94836d748..2da7c6336 100755 --- a/src/sbin/bcfg2-yum-helper +++ b/src/sbin/bcfg2-yum-helper @@ -5,8 +5,6 @@ the right way to get around that in long-running processes it to have a short-lived helper. No, seriously -- check out the yum-updatesd code. It's pure madness. """ -__revision__ = '$Revision$' - import os import sys import yum @@ -19,7 +17,26 @@ try: except ImportError: import simplejson as json -logger = logging.getLogger('bcfg2-yum-helper') +LOGGER = None + +def get_logger(verbose=0): + """ set up logging according to the verbose level given on the + command line """ + global LOGGER + if LOGGER is None: + LOGGER = logging.getLogger(sys.argv[0]) + stderr = logging.StreamHandler() + if verbose: + level = logging.DEBUG + else: + level = logging.WARNING + LOGGER.setLevel(level) + LOGGER.addHandler(stderr) + syslog = logging.handlers.SysLogHandler("/dev/log") + syslog.setFormatter(logging.Formatter("%(name)s: %(message)s")) + LOGGER.addHandler(syslog) + return LOGGER + class DepSolver(object): def __init__(self, cfgfile, verbose=1): @@ -28,8 +45,10 @@ class DepSolver(object): try: self.yumbase.preconf.debuglevel = verbose self.yumbase.preconf.fn = cfgfile + self.yumbase._getConfig() except AttributeError: self.yumbase._getConfig(cfgfile, debuglevel=verbose) + self.logger = get_logger(verbose) def get_groups(self): try: @@ -38,7 +57,7 @@ class DepSolver(object): return ["noarch"] def set_groups(self, groups): - self._groups = set(groups).add("noarch") + self._groups = set(groups).union(["noarch"]) groups = property(get_groups, set_groups) @@ -59,13 +78,13 @@ class DepSolver(object): matches = self.yumbase.pkgSack.returnNewestByName(name=package) except yum.Errors.PackageSackError: if not silent: - logger.warning("Packages: Package '%s' not found" % - self.get_package_name(package)) + self.logger.warning("Package '%s' not found" % + self.get_package_name(package)) matches = [] except yum.Errors.RepoError: err = sys.exc_info()[1] - logger.error("Packages: Temporary failure loading metadata for " - "'%s': %s" % (self.get_package_name(package), err)) + self.logger.error("Temporary failure loading metadata for %s: %s" % + (self.get_package_name(package), err)) matches = [] pkgs = self._filter_arch(matches) @@ -83,8 +102,8 @@ class DepSolver(object): deps.difference_update([dep for dep in deps if pkg.checkPrco('provides', dep)]) else: - logger.error("Packages: No package available: %s" % - self.get_package_name(package)) + self.logger.error("No package available: %s" % + self.get_package_name(package)) return deps def get_provides(self, required, all=False, silent=False): @@ -96,16 +115,15 @@ class DepSolver(object): self.yumbase.whatProvides(*required).returnNewestByNameArch() except yum.Errors.NoMoreMirrorsRepoError: err = sys.exc_info()[1] - logger.error("Packages: Temporary failure loading metadata for " - "'%s': %s" % - (self.get_package_name(required), err)) + self.logger.error("Temporary failure loading metadata for %s: %s" % + (self.get_package_name(required), err)) return [] if prov and not all: prov = self._filter_provides(required, prov) elif not prov and not silent: - logger.error("Packages: No package provides %s" % - self.get_package_name(required)) + self.logger.error("No package provides %s" % + self.get_package_name(required)) return prov def get_group(self, group, ptype="default"): @@ -116,11 +134,11 @@ class DepSolver(object): if self.yumbase.comps.has_group(group): group = self.yumbase.comps.return_group(group) else: - logger.warning("Packages: '%s' is not a valid group" % group) + self.logger.warning("%s is not a valid group" % group) return [] except yum.Errors.GroupsError: err = sys.exc_info()[1] - logger.warning("Packages: %s" % err) + self.logger.warning(err) return [] if ptype == "default": @@ -134,7 +152,7 @@ class DepSolver(object): elif ptype == "optional" or ptype == "all": return group.packages else: - logger.warning("Unknown group package type '%s'" % ptype) + self.logger.warning("Unknown group package type '%s'" % ptype) return [] def _filter_provides(self, package, providers): @@ -156,14 +174,24 @@ class DepSolver(object): # provider of perl(lib). rv = [] for pkg in providers: - if self.get_package_object(pkg.name) == pkg: + found = self.get_package_object(pkg.name) + if found == pkg or found.pkgtup == pkg.pkgtup: rv.append(pkg) + else: + self.logger.debug("Skipping %s, not newest (%s)" % + (pkg, found)) else: rv = providers return [p.name for p in rv] def _filter_arch(self, packages): - matching = [pkg for pkg in packages if pkg.arch in self.groups] + matching = [] + for pkg in packages: + if pkg.arch in self.groups: + matching.append(pkg) + else: + self.logger.debug("%s has non-matching architecture (%s)" % + (pkg, pkg.arch)) if matching: return matching else: @@ -180,10 +208,7 @@ class DepSolver(object): else: return str(package) - def complete(self, packagelist, groups=None): - if groups is None: - groups = [] - + def complete(self, packagelist): packages = set() pkgs = set(packagelist) requires = set() @@ -202,12 +227,17 @@ class DepSolver(object): if not self.is_package(package): # try this package out as a requirement + self.logger.debug("Adding requirement %s" % package) requires.add((package, None, (None, None, None))) continue packages.add(package) reqs = set(self.get_deps(package)).difference(satisfied) if reqs: + self.logger.debug("Adding requirements for %s: %s" % + (package, + ",".join([self.get_package_name(r) + for r in reqs]))) requires.update(reqs) reqs_satisfied = set() @@ -222,8 +252,8 @@ class DepSolver(object): reqs_satisfied.add(req) continue - logger.debug("Packages: Handling requirement '%s'" % - self.get_package_name(req)) + self.logger.debug("Handling requirement '%s'" % + self.get_package_name(req)) providers = list(set(self.get_provides(req))) if len(providers) > 1: # hopefully one of the providing packages is already @@ -237,16 +267,24 @@ class DepSolver(object): if len(best) == 1: providers = best elif not final_pass: - # found no "best" package, so defer + self.logger.debug("%s has multiple providers: %s" % + (self.get_package_name(req), + providers)) + self.logger.debug("No provider is obviously the " + "best; deferring") providers = None - # else: found no "best" package, but it's the - # final pass, so include them all + else: + # found no "best" package, but it's the + # final pass, so include them all + self.logger.debug("Found multiple providers for %s," + "including all" % + self.get_package_name(req)) if providers: - logger.debug("Packages: Requirement '%s' satisfied by %s" % - (self.get_package_name(req), - ",".join([self.get_package_name(p) - for p in providers]))) + self.logger.debug("Requirement '%s' satisfied by %s" % + (self.get_package_name(req), + ",".join([self.get_package_name(p) + for p in providers]))) newpkgs = set(providers).difference(packages) if newpkgs: for package in newpkgs: @@ -257,6 +295,8 @@ class DepSolver(object): reqs_satisfied.add(req) elif providers is not None: # nothing provided this requirement at all + self.logger.debug("Nothing provides %s" % + self.get_package_name(req)) unknown.add(req) reqs_satisfied.add(req) # else, defer @@ -283,7 +323,7 @@ class DepSolver(object): # many files were deleted. so useful. thanks, yum. msg = getattr(self.yumbase, "clean%s" % mdtype)()[1][0] if not msg.startswith("0 "): - logger.info("Packages: %s" % msg) + self.logger.info(msg) def main(): @@ -291,15 +331,15 @@ def main(): parser.add_option("-c", "--config", help="Config file") parser.add_option("-v", "--verbose", help="Verbosity level", action="count") (options, args) = parser.parse_args() + logger = get_logger(options.verbose) try: cmd = args[0] except IndexError: - logger.error("bcfg2-yum-helper: No command given") + logger.error("No command given") return 1 if not os.path.exists(options.config): - logger.error("bcfg2-yum-helper: Config file %s not found" % - options.config) + logger.error("Config file %s not found" % options.config) return 1 depsolver = DepSolver(options.config, options.verbose) @@ -308,8 +348,8 @@ def main(): print json.dumps(True) elif cmd == "complete": data = json.loads(sys.stdin.read()) - (packages, unknown) = depsolver.complete(data['packages'], - groups=data['groups']) + depsolver.groups = data['groups'] + (packages, unknown) = depsolver.complete(data['packages']) print json.dumps(dict(packages=list(packages), unknown=list(unknown))) elif cmd == "is_virtual_package": |