diff options
Diffstat (limited to 'src/sbin')
-rwxr-xr-x | src/sbin/bcfg2 | 118 | ||||
-rwxr-xr-x | src/sbin/bcfg2-admin | 31 | ||||
-rwxr-xr-x | src/sbin/bcfg2-build-reports | 2 | ||||
-rwxr-xr-x | src/sbin/bcfg2-crypt | 356 | ||||
-rwxr-xr-x | src/sbin/bcfg2-info | 255 | ||||
-rwxr-xr-x | src/sbin/bcfg2-lint | 125 | ||||
-rwxr-xr-x | src/sbin/bcfg2-ping-sweep | 2 | ||||
-rwxr-xr-x | src/sbin/bcfg2-reports | 576 | ||||
-rwxr-xr-x | src/sbin/bcfg2-server | 50 | ||||
-rwxr-xr-x | src/sbin/bcfg2-test | 21 | ||||
-rwxr-xr-x | src/sbin/bcfg2-yum-helper | 2 |
11 files changed, 885 insertions, 653 deletions
diff --git a/src/sbin/bcfg2 b/src/sbin/bcfg2 index 1d1cc8424..2a7e5f585 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 @@ -28,10 +27,6 @@ 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""" @@ -39,46 +34,40 @@ class Client: 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, - 'timeout': Bcfg2.Options.CLIENT_TIMEOUT, - } - + + optinfo = \ + dict(extra=Bcfg2.Options.CLIENT_EXTRA_DISPLAY, + quick=Bcfg2.Options.CLIENT_QUICK, + lockfile=Bcfg2.Options.LOCKFILE, + drivers=Bcfg2.Options.CLIENT_DRIVERS, + dryrun=Bcfg2.Options.CLIENT_DRYRUN, + paranoid=Bcfg2.Options.CLIENT_PARANOID, + bundle=Bcfg2.Options.CLIENT_BUNDLE, + skipbundle=Bcfg2.Options.CLIENT_SKIPBUNDLE, + bundle_quick=Bcfg2.Options.CLIENT_BUNDLEQUICK, + indep=Bcfg2.Options.CLIENT_INDEP, + skipindep=Bcfg2.Options.CLIENT_SKIPINDEP, + file=Bcfg2.Options.CLIENT_FILE, + interactive=Bcfg2.Options.INTERACTIVE, + cache=Bcfg2.Options.CLIENT_CACHE, + profile=Bcfg2.Options.CLIENT_PROFILE, + remove=Bcfg2.Options.CLIENT_REMOVE, + 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, + omit_lock_check=Bcfg2.Options.OMIT_LOCK_CHECK, + 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, + timeout=Bcfg2.Options.CLIENT_TIMEOUT, + decision_list=Bcfg2.Options.CLIENT_DECISION_LIST) + optinfo.update(Bcfg2.Options.CLI_COMMON_OPTIONS) + optinfo.update(Bcfg2.Options.DRIVER_OPTIONS) self.setup = Bcfg2.Options.OptionParser(optinfo) self.setup.parse(sys.argv[1:]) @@ -94,30 +83,29 @@ class Client: Bcfg2.Logger.setup_logging('bcfg2', to_syslog=False, level=level, - to_file=self.setup['filelog']) + to_file=self.setup['logging']) self.logger = logging.getLogger('bcfg2') self.logger.debug(self.setup) - if self.setup['bundle-quick']: + if self.setup['bundle_quick']: if self.setup['bundle'] == []: self.logger.error("-Q option requires -b") raise SystemExit(1) - elif self.setup['remove'] != False: + elif self.setup['remove']: 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; removed services will only be disabled") - if self.setup['remove'] not in [False, - 'all', - 'Services', - 'Packages', - 'services', - 'packages']: - self.logger.error("Got unknown argument %s for -r" % (self.setup['remove'])) - if (self.setup["file"] != False) and (self.setup["cache"] != False): + if self.setup['remove'] and 'services' in self.setup['remove'].lower(): + self.logger.error("Service removal is nonsensical; " + "removed services will only be disabled") + if (self.setup['remove'] and + self.setup['remove'].lower() not in ['all', 'services', + 'packages']): + self.logger.error("Got unknown argument %s for -r" % + self.setup['remove']) + if self.setup["file"] and self.setup["cache"]: print("cannot use -f and -c together") raise SystemExit(1) if not self.setup['server'].startswith('https://'): @@ -283,10 +271,12 @@ class Client: self.fatal_error("Server error: %s" % (self.config.text)) return(1) - if self.setup['bundle-quick']: + 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']] + [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, @@ -294,7 +284,7 @@ class Client: times, self.setup['drivers'], self.setup['dryrun']) - if not self.setup['omit-lock-check']: + if not self.setup['omit_lock_check']: #check lock here try: lockfile = open(self.setup['lockfile'], 'w') @@ -310,7 +300,7 @@ class Client: # execute the said configuration self.tools.Execute() - if not self.setup['omit-lock-check']: + if not self.setup['omit_lock_check']: #unlock here if lockfile: try: @@ -319,7 +309,7 @@ class Client: except OSError: self.logger.error("Failed to unlock lockfile %s" % lockfile.name) - if not self.setup['file'] and not self.setup['bundle-quick']: + if not self.setup['file'] and not self.setup['bundle_quick']: # upload statistics feedback = self.tools.GenerateStats() diff --git a/src/sbin/bcfg2-admin b/src/sbin/bcfg2-admin index 5cb69d747..9b28d9bd5 100755 --- a/src/sbin/bcfg2-admin +++ b/src/sbin/bcfg2-admin @@ -3,7 +3,6 @@ import sys import logging -import Bcfg2.Server.Core import Bcfg2.Logger import Bcfg2.Options import Bcfg2.Server.Admin @@ -31,32 +30,27 @@ def create_description(): for mode in modes: try: description.write((" %-15s %s\n" % - (mode, mode_import(mode).__shorthelp__))) + (mode, mode_import(mode).__shorthelp__))) except (ImportError, SystemExit): pass return description.getvalue() def main(): - optinfo = { - '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, - } + optinfo = dict() + optinfo.update(Bcfg2.Options.CLI_COMMON_OPTIONS) + optinfo.update(Bcfg2.Options.SERVER_COMMON_OPTIONS) setup = Bcfg2.Options.OptionParser(optinfo) # override default help message to include description of all modes - setup.hm = "Usage:\n %s\n%s" % (setup.buildHelpMessage(), - create_description()) + setup.hm = "%s\n%s" % (setup.buildHelpMessage(), create_description()) setup.parse(sys.argv[1:]) - log_args = dict(to_syslog=False, to_console=logging.WARNING) - if setup['verbose']: - log_args['to_console'] = logging.DEBUG + if setup['debug']: + level = logging.DEBUG + elif setup['verbose']: + level = logging.INFO + else: + level = logging.WARNING + Bcfg2.Logger.setup_logging('bcfg2-admin', to_syslog=False, level=level) # Provide help if requested or no args were specified if (not setup['args'] or len(setup['args']) < 1 or @@ -86,7 +80,6 @@ def main(): mode.bcore.shutdown() else: log.error("Unknown mode %s" % setup['args'][0]) - print("Usage:\n %s" % setup.buildHelpMessage()) print(create_description()) raise SystemExit(1) 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-crypt b/src/sbin/bcfg2-crypt new file mode 100755 index 000000000..89dfe3e2a --- /dev/null +++ b/src/sbin/bcfg2-crypt @@ -0,0 +1,356 @@ +#!/usr/bin/env python +""" helper for encrypting/decrypting Cfg and Properties files """ + +import os +import sys +import logging +import lxml.etree +import Bcfg2.Logger +import Bcfg2.Options +import Bcfg2.Encryption + +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 Encryptor(object): + def __init__(self, setup): + self.setup = setup + self.logger = get_logger() + self.passphrase = None + self.pname = None + + def get_encrypted_filename(self, plaintext_filename): + return plaintext_filename + + def get_plaintext_filename(self, encrypted_filename): + return encrypted_filename + + def chunk(self, data): + yield data + + def unchunk(self, data, original): + return data[0] + + def set_passphrase(self): + if (not self.setup.cfp.has_section("encryption") or + self.setup.cfp.options("encryption") == 0): + self.logger.error("No passphrases available in %s" % + self.setup['configfile']) + return False + + if self.passphrase: + self.logger.debug("Using previously determined passphrase %s" % + self.pname) + return True + + if self.setup['passphrase']: + self.pname = self.setup['passphrase'] + + if self.pname: + if self.setup.cfp.has_option("encryption", self.pname): + self.passphrase = self.setup.cfp.get("encryption", + self.pname) + self.logger.debug("Using passphrase %s specified on command " + "line" % self.pname) + return True + else: + self.logger.error("Could not find passphrase %s in %s" % + (self.pname, self.setup['configfile'])) + return False + else: + pnames = self.setup.cfp.options("encryption") + if len(pnames) == 1: + self.passphrase = self.setup.cfp.get(pnames[0]) + self.pname = pnames[0] + self.logger.info("Using passphrase %s" % pnames[0]) + return True + self.logger.info("No passphrase could be determined") + return False + + def encrypt(self, fname): + try: + plaintext = open(fname).read() + except IOError: + err = sys.exc_info()[1] + self.logger.error("Error reading %s, skipping: %s" % (fname, err)) + return False + + self.set_passphrase() + + crypted = [] + for chunk in self.chunk(plaintext): + try: + passphrase, pname = self.get_passphrase(chunk) + except TypeError: + return False + + crypted.append(self._encrypt(chunk, passphrase, name=pname)) + + new_fname = self.get_encrypted_filename(fname) + try: + open(new_fname, "wb").write(self.unchunk(crypted, plaintext)) + self.logger.info("Wrote encrypted data to %s" % new_fname) + return True + except IOError: + err = sys.exc_info()[1] + self.logger.error("Error writing encrypted data from %s to %s: %s" % + (fname, new_fname, err)) + return False + + def _encrypt(self, plaintext, passphrase, name=None): + return Bcfg2.Encryption.ssl_encrypt(plaintext, passphrase) + + def decrypt(self, fname): + try: + crypted = open(fname).read() + except IOError: + err = sys.exc_info()[1] + self.logger.error("Error reading %s, skipping: %s" % (fname, err)) + return False + + self.set_passphrase() + + plaintext = [] + for chunk in self.chunk(crypted): + try: + passphrase, pname = self.get_passphrase(chunk) + try: + plaintext.append(self._decrypt(chunk, passphrase)) + except Bcfg2.Encryption.EVPError: + self.logger.info("Could not decrypt %s with the specified " + "passphrase" % fname) + return False + except: + err = sys.exc_info()[1] + self.logger.error("Error decrypting %s: %s" % (fname, err)) + return False + except TypeError: + pchunk = None + for pname in self.setup.cfp.options('encryption'): + self.logger.debug("Trying passphrase %s" % pname) + passphrase = self.setup.cfp.get('encryption', pname) + try: + pchunk = self._decrypt(chunk, passphrase) + break + except Bcfg2.Encryption.EVPError: + pass + except: + err = sys.exc_info()[1] + self.logger.error("Error decrypting %s: %s" % + (fname, err)) + if pchunk is not None: + plaintext.append(pchunk) + else: + self.logger.error("Could not decrypt %s with any " + "passphrase in %s" % + (fname, self.setup['configfile'])) + return False + + new_fname = self.get_plaintext_filename(fname) + try: + open(new_fname, "wb").write(self.unchunk(plaintext, crypted)) + self.logger.info("Wrote decrypted data to %s" % new_fname) + return True + except IOError: + err = sys.exc_info()[1] + self.logger.error("Error writing encrypted data from %s to %s: %s" % + (fname, new_fname, err)) + return False + + def get_passphrase(self, chunk): + pname = self._get_passphrase(chunk) + if not self.pname: + if not pname: + self.logger.info("No passphrase given on command line or " + "found in file") + return False + elif self.setup.cfp.has_option("encryption", pname): + passphrase = self.setup.cfp.get("encryption", pname) + else: + self.logger.error("Could not find passphrase %s in %s" % + (pname, self.setup['configfile'])) + return False + else: + pname = self.pname + passphrase = self.passphrase + if self.pname != pname: + self.logger.warning("Passphrase given on command line (%s) " + "differs from passphrase embedded in " + "file (%s), using command-line option" % + (self.pname, pname)) + return (passphrase, pname) + + def _get_passphrase(self, chunk): + return None + + def _decrypt(self, crypted, passphrase): + return Bcfg2.Encryption.ssl_decrypt(crypted, passphrase) + + +class CfgEncryptor(Encryptor): + def get_encrypted_filename(self, plaintext_filename): + return plaintext_filename + ".crypt" + + def get_plaintext_filename(self, encrypted_filename): + if encrypted_filename.endswith(".crypt"): + return encrypted_filename[:-6] + else: + return Encryptor.get_plaintext_filename(self, encrypted_filename) + + +class PropertiesEncryptor(Encryptor): + def _encrypt(self, plaintext, passphrase, name=None): + # plaintext is an lxml.etree._Element + if name is None: + name = "true" + if plaintext.text and plaintext.text.strip(): + plaintext.text = Bcfg2.Encryption.ssl_encrypt(plaintext.text, + passphrase) + plaintext.set("encrypted", name) + return plaintext + + def chunk(self, data): + xdata = lxml.etree.XML(data) + if self.setup['xpath']: + elements = xdata.xpath(self.setup['xpath']) + else: + elements = xdata.xpath('//*[@encrypted]') + if not elements: + elements = list(xdata.getiterator()) + # this is not a good use of a generator, but we need to + # generate the full list of elements in order to ensure that + # some exist before we know what to return + for elt in elements: + yield elt + + def unchunk(self, data, original): + # Properties elements are modified in-place, so we don't + # actually need to unchunk anything + xdata = data[0] + # find root element + while xdata.getparent() != None: + xdata = xdata.getparent() + xdata.set("encryption", "true") + return lxml.etree.tostring(xdata) + + def _get_passphrase(self, chunk): + pname = chunk.get("encrypted") or chunk.get("encryption") + if pname and pname.lower() != "true": + return pname + return None + + def _decrypt(self, crypted, passphrase): + # crypted is in lxml.etree._Element + if not crypted.text or not crypted.text.strip(): + self.logger.warning("Skipping empty element %s" % crypted.tag) + return crypted + rv = Bcfg2.Encryption.ssl_decrypt(crypted.text, passphrase) + crypted.text = rv + return crypted + + +def main(): + optinfo = dict() + optinfo.update(Bcfg2.Options.CRYPT_OPTIONS) + optinfo.update(Bcfg2.Options.CLI_COMMON_OPTIONS) + setup = Bcfg2.Options.OptionParser(optinfo) + setup.hm = " bcfg2-crypt [options] <filename>\nOptions:\n%s" % \ + setup.buildHelpMessage() + setup.parse(sys.argv[1:]) + + if not setup['args']: + print(setup.hm) + raise SystemExit(1) + elif setup['encrypt'] and setup['decrypt']: + print("You cannot specify both --encrypt) and --decrypt") + raise SystemExit(1) + elif setup['cfg'] and setup['properties']: + print("You cannot specify both --cfg and --properties") + raise SystemExit(1) + elif setup['cfg'] and setup['properties']: + print("Specifying --xpath with --cfg is nonsensical, ignoring --xpath") + setup['xpath'] = Bcfg2.Options.CRYPT_XPATH.default + elif setup['decrypt'] and setup['remove']: + print("--remove cannot be used with --decrypt, ignoring") + setup['remove'] = Bcfg2.Options.CRYPT_REMOVE.default + + logger = get_logger(setup['verbose']) + + props_crypt = PropertiesEncryptor(setup) + cfg_crypt = CfgEncryptor(setup) + + for fname in setup['args']: + if not os.path.exists(fname): + logger.error("%s does not exist, skipping" % fname) + continue + + # figure out if we need to encrypt this as a Properties file + # or as a Cfg file + props = False + if setup['properties']: + props = True + elif setup['cfg']: + props = False + elif fname.endswith(".xml"): + try: + xroot = lxml.etree.parse(fname).getroot() + if xroot.tag == "Properties": + props = True + else: + props = False + except IOError: + err = sys.exc_info()[1] + logger.error("Error reading %s, skipping: %s" % (fname, err)) + continue + except lxml.etree.XMLSyntaxError: + props = False + else: + props = False + + if props: + encryptor = props_crypt + else: + encryptor = cfg_crypt + + if setup['encrypt']: + if not encryptor.encrypt(fname): + print("Failed to encrypt %s, skipping" % fname) + elif setup['decrypt']: + if not encryptor.decrypt(fname): + print("Failed to decrypt %s, skipping" % fname) + else: + logger.info("Neither --encrypt nor --decrypt specified, " + "determining mode") + if not encryptor.decrypt(fname): + logger.info("Failed to decrypt %s, trying encryption" % fname) + if not encryptor.encrypt(fname): + print("Failed to encrypt %s, skipping" % fname) + + if setup['remove'] and encryptor.get_encrypted_filename(fname) != fname: + try: + os.unlink(fname) + except IOError: + err = sys.exc_info()[1] + logger.error("Error removing %s: %s" % (fname, err)) + continue + +if __name__ == '__main__': + sys.exit(main()) diff --git a/src/sbin/bcfg2-info b/src/sbin/bcfg2-info index 656532155..617584d3d 100755 --- a/src/sbin/bcfg2-info +++ b/src/sbin/bcfg2-info @@ -1,17 +1,17 @@ #!/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 +import traceback +from code import InteractiveConsole try: try: @@ -27,14 +27,20 @@ import Bcfg2.Logger import Bcfg2.Options import Bcfg2.Server.Core import Bcfg2.Server.Plugins.Metadata -import Bcfg2.Server.Plugins.SGenshi import Bcfg2.Server.Plugin +try: + import Bcfg2.Server.Plugins.SGenshi + has_genshi = True +except ImportError: + has_genshi = False + 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 @@ -51,8 +57,7 @@ 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> @@ -78,10 +83,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): @@ -90,6 +97,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 \ @@ -109,12 +140,12 @@ 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, - cfile='/etc/bcfg2.conf', filemonitor='default'): + filemonitor='default', setup=None): cmd.Cmd.__init__(self) try: Bcfg2.Server.Core.Core.__init__(self, repo, plgs, passwd, - encoding, cfile=cfile, - filemonitor=filemonitor) + encoding, filemonitor=filemonitor, + setup=setup) if event_debug: self.fam.debug = True except Bcfg2.Server.Core.CoreInitError: @@ -177,7 +208,11 @@ class infoCore(cmd.Cmd, Bcfg2.Server.Core.Core): else: raise ImportError except ImportError: - sh.interact() + try: + import bpython.cli + bpython.cli.main(locals_=locals()) + except ImportError: + sh.interact() def do_quit(self, _): """ @@ -199,10 +234,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() @@ -260,50 +291,101 @@ class infoCore(cmd.Cmd, Bcfg2.Server.Core.Core): def do_buildall(self, args): alist = args.split() - flags = [] - for arg in alist: - if arg == '-f': - alist.remove('-f') - flags.append(arg) - if len(alist) != 1: - print("Usage: buildall [-f] <directory>") + if len(alist) < 1: + print("Usage: buildall <directory> [<hostnames*>]") return - if not os.path.exists(alist[0]): - try: - os.mkdir(alist[0]) - except OSError: - err = sys.exc_info()[1] - logger.error("Could not create %s: %s" % (alist[0], err)) - for client in self.metadata.clients: - self.do_build("%s %s %s/%s.xml" % (" ".join(flags), - client, args, client)) + + destdir = alist[0] + 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(alist) > 1: + clients = getClientList(alist[1:]) + else: + clients = list(self.metadata.clients.keys()) + for client in clients: + self.do_build("%s %s" % (client, os.path.join(destdir, + client + ".xml"))) + + 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: %s" % + (fname, client, traceback.format_exc().splitlines()[-1])) + raise def do_buildbundle(self, args): """Render a bundle for client.""" @@ -313,8 +395,9 @@ class infoCore(cmd.Cmd, Bcfg2.Server.Core.Core): metadata = self.build_metadata(client) if bname in self.plugins['Bundler'].entries: bundle = self.plugins['Bundler'].entries[bname] - if isinstance(bundle, - Bcfg2.Server.Plugins.SGenshi.SGenshiTemplateFile): + if (has_genshi and + isinstance(bundle, + Bcfg2.Server.Plugins.SGenshi.SGenshiTemplateFile)): stream = bundle.template.generate(metadata=metadata) print(stream.render("xml")) else: @@ -523,31 +606,8 @@ class infoCore(cmd.Cmd, Bcfg2.Server.Core.Core): 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("") - + print collection.sourcelist() + def do_profile(self, arg): """.""" if not have_profile: @@ -568,32 +628,16 @@ class infoCore(cmd.Cmd, Bcfg2.Server.Core.Core): if __name__ == '__main__': Bcfg2.Logger.setup_logging('bcfg2-info', to_syslog=False) - optinfo = { - 'configfile': Bcfg2.Options.CFILE, - 'help': Bcfg2.Options.HELP, - '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, - 'interactive': Bcfg2.Options.INTERACTIVE, - } + optinfo = dict(profile=Bcfg2.Options.CORE_PROFILE, + mconnect=Bcfg2.Options.SERVER_MCONNECT, + interactive=Bcfg2.Options.INTERACTIVE) + optinfo.update(Bcfg2.Options.CLI_COMMON_OPTIONS) + optinfo.update(Bcfg2.Options.SERVER_COMMON_OPTIONS) setup = Bcfg2.Options.OptionParser(optinfo) - setup.hm = "Usage:\n %s\n%s" % (setup.buildHelpMessage(), - USAGE) + setup.hm = "\n".join([" bcfg2-info [options] [command <command args>]", + "Options:", + setup.buildHelpMessage(), + USAGE]) setup.parse(sys.argv[1:]) if setup['args'] and setup['args'][0] == 'help': @@ -603,15 +647,14 @@ if __name__ == '__main__': prof = profile.Profile() loop = prof.runcall(infoCore, setup['repo'], setup['plugins'], setup['password'], setup['encoding'], - setup['event debug'], cfile=setup['configfile'], - filemonitor=setup['filemonitor']) + setup['debug'], setup['filemonitor'], + setup) displayTrace(prof) else: if setup['profile']: print("Profiling functionality not available.") loop = infoCore(setup['repo'], setup['plugins'], setup['password'], - setup['encoding'], setup['event debug'], - cfile=setup['configfile'], - filemonitor=setup['filemonitor']) + setup['encoding'], setup['debug'], + setup['filemonitor'], setup) loop.Run(setup['args']) diff --git a/src/sbin/bcfg2-lint b/src/sbin/bcfg2-lint index 2d371f4aa..423c63ba3 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 @@ -63,46 +62,36 @@ def get_errorhandler(config): def load_server(setup): """ load server """ core = Bcfg2.Server.Core.Core(setup['repo'], setup['plugins'], - setup['password'], setup['encoding']) - if setup['event debug']: - core.fam.debug = True + setup['password'], setup['encoding'], + filemonitor=setup['filemonitor'], + setup=setup) core.fam.handle_events_in_interval(4) return core +def load_plugin(module, obj_name=None): + parts = module.split(".") + if obj_name is None: + obj_name = parts[-1] + + try: + mod = __import__(module) + except ImportError: + err = sys.exc_info()[1] + logger.error("Failed to load plugin %s: %s" % (obj_name, err)) + raise + + for p in parts[1:]: + mod = getattr(mod, p) + return getattr(mod, obj_name) + if __name__ == '__main__': - optinfo = { - 'configfile': Bcfg2.Options.CFILE, - 'help': Bcfg2.Options.HELP, - 'verbose': Bcfg2.Options.VERBOSE, - } - optinfo.update({ - 'event debug': Bcfg2.Options.DEBUG, - 'encoding': Bcfg2.Options.ENCODING, - # Server options - 'repo': Bcfg2.Options.SERVER_REPOSITORY, - 'plugins': Bcfg2.Options.SERVER_PLUGINS, - '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, - 'stdin': Bcfg2.Options.FILES_ON_STDIN, - 'schema': Bcfg2.Options.SCHEMA_PATH, - 'config': Bcfg2.Options.Option('Specify bcfg2-lint configuration file', - '/etc/bcfg2-lint.conf', - cmd='--lint-config', - odesc='<conffile>', - long_arg=True), - 'showerrors': Bcfg2.Options.Option('Show error handling', False, - cmd='--list-errors', - long_arg=True), - }) + optinfo = dict(config=Bcfg2.Options.LINT_CONFIG, + showerrors=Bcfg2.Options.LINT_SHOW_ERRORS, + stdin=Bcfg2.Options.LINT_FILES_ON_STDIN, + schema=Bcfg2.Options.SCHEMA_PATH, + plugins=Bcfg2.Options.SERVER_PLUGINS) + optinfo.update(Bcfg2.Options.CLI_COMMON_OPTIONS) + optinfo.update(Bcfg2.Options.SERVER_COMMON_OPTIONS) setup = Bcfg2.Options.OptionParser(optinfo) setup.parse(sys.argv[1:]) @@ -115,53 +104,38 @@ if __name__ == '__main__': config.read(setup['configfile']) config.read(setup['config']) - if setup['showerrors']: - if config.has_section("errors"): - econf = dict(config.items("errors")) - else: - econf = dict() - - print("%-35s %-35s" % ("Error name", "Handler (Default)")) - for err, default in Bcfg2.Server.Lint.ErrorHandler._errors.items(): - if err in econf and econf[err] != default: - handler = "%s (%s)" % (econf[err], default) - else: - handler = default - print("%-35s %-35s" % (err, handler)) - raise SystemExit(0) - # get list of plugins to run if setup['args']: - allplugins = setup['args'] + plugin_list = setup['args'] elif "bcfg2-repo-validate" in sys.argv[0]: - allplugins = 'Duplicates,RequiredAttrs,Validate'.split(',') + plugin_list = 'Duplicates,RequiredAttrs,Validate'.split(',') else: try: - allplugins = config.get('lint', 'plugins').split(',') + plugin_list = config.get('lint', 'plugins').split(',') except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): - allplugins = Bcfg2.Server.Lint.__all__ + plugin_list = Bcfg2.Server.Lint.__all__ if setup['stdin']: files = [s.strip() for s in sys.stdin.readlines()] else: files = None - # load plugins - serverplugins = {} - serverlessplugins = {} - for plugin_name in allplugins: + # load plugins specified in the config first + allplugins = dict() + for plugin in plugin_list: + allplugins[plugin] = load_plugin("Bcfg2.Server.Lint." + plugin) + + # load lint plugins bundled with bcfg2-server plugins + for plugin in setup['plugins']: try: - mod = getattr(__import__("Bcfg2.Server.Lint.%s" % - (plugin_name)).Server.Lint, plugin_name) - except ImportError: - try: - mod = __import__(plugin_name) - except Exception: - err = sys.exc_info()[1] - logger.error("Failed to load plugin %s: %s" % (plugin_name, - err)) - raise SystemExit(1) - plugin = getattr(mod, plugin_name) + allplugins[plugin] = load_plugin("Bcfg2.Server.Plugins." + plugin, + obj_name=plugin + "Lint") + except AttributeError: + pass + + serverplugins = dict() + serverlessplugins = dict() + for plugin_name, plugin in allplugins.items(): if [c for c in inspect.getmro(plugin) if c == Bcfg2.Server.Lint.ServerPlugin]: serverplugins[plugin_name] = plugin @@ -170,6 +144,15 @@ if __name__ == '__main__': errorhandler = get_errorhandler(config) + if setup['showerrors']: + for plugin in serverplugins.values() + serverlessplugins.values(): + errorhandler.RegisterErrors(getattr(plugin, 'Errors')()) + + print("%-35s %-35s" % ("Error name", "Handler")) + for err, handler in errorhandler._handlers.items(): + print("%-35s %-35s" % (err, handler.__name__)) + raise SystemExit(0) + run_serverless_plugins(serverlessplugins, errorhandler=errorhandler, config=config, setup=setup) 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 3920f519a..cb553c0ba 100755 --- a/src/sbin/bcfg2-reports +++ b/src/sbin/bcfg2-reports @@ -1,9 +1,10 @@ #!/usr/bin/env python """Query reporting system for client status.""" -__revision__ = '$Revision$' import os import sys +import datetime +from optparse import OptionParser, OptionGroup, make_option from Bcfg2.Bcfg2Py3k import ConfigParser try: @@ -22,376 +23,277 @@ 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 -import getopt -import datetime -import fileinput - -usage = """Usage: 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 --m NAME : single-host mode - shows modified entries from the - current interaction of NAME --s NAME : single-host mode - shows bad, modified, and extra - entries from the current interaction of NAME --t NAME : single-host mode - shows total number of managed and - good 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 ---modifiedentry=KIND,NAME : shows only hosts whose current interaction has - modified 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 -""" - -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 totalcompare(client1, client2): - """Compares two clients by their total entry counts.""" - return cmp(client2.current_interaction.totalcount, \ - client1.current_interaction.totalcount) - -def goodcompare(client1, client2): - """Compares two clients by their good entry counts.""" - return cmp(client2.current_interaction.goodcount, \ - client1.current_interaction.goodcount) +from Bcfg2.Server.Reports.reports.models import (Client, Entries_interactions, + Entries, TYPE_CHOICES) -def badcompare(client1, client2): - """Compares two clients by their bad entry counts.""" - return cmp(client2.current_interaction.totalcount - \ - client2.current_interaction.goodcount, \ - client1.current_interaction.totalcount - \ - client1.current_interaction.goodcount) +def hosts_by_entry_type(clients, etype, entryspec): + result = [] + for entry in entryspec: + for client in clients: + items = getattr(client.current_interaction, etype)() + for item in items: + if (item.entry.kind == entry[0] and + item.entry.name == entry[1]): + result.append(client) + return result -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) - elif crit == 'total': - comp = totalcompare(client1, client2) - elif crit == 'good': - comp = goodcompare(client1, client2) - elif crit == 'bad': - comp = badcompare(client1, client2) - - if comp != 0: - return comp - - return 0 - -def print_fields(fields, cli, max_name, entrydict): +def print_fields(fields, client, fmt, extra=None): """ - Prints the fields specified in fields of cli, max_name + Prints the fields specified in fields of client, 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 = [] + if extra is None: + extra = dict() for field in fields: if field == 'time': - fdata.append(str(cli.current_interaction.timestamp)) + fdata.append(str(client.current_interaction.timestamp)) elif field == 'state': - if cli.current_interaction.isclean(): + if client.current_interaction.isclean(): fdata.append("clean") else: fdata.append("dirty") elif field == 'total': - fdata.append("%5d" % cli.current_interaction.totalcount) + fdata.append(client.current_interaction.totalcount) elif field == 'good': - fdata.append("%5d" % cli.current_interaction.goodcount) + fdata.append(client.current_interaction.goodcount) + elif field == 'modified': + fdata.append(client.current_interaction.modified_entry_count()) + elif field == 'extra': + fdata.append(client.current_interaction.extra_entry_count()) elif field == 'bad': - fdata.append("%5d" % cli.current_interaction.totalcount \ - - cli.current_interaction.goodcount) + fdata.append((client.current_interaction.badcount())) else: try: - fdata.append(getattr(cli, field)) + fdata.append(getattr(client, field)) except: - fdata.append("N/A") + fdata.append(extra.get(field, "N/A")) - display = fmt % tuple(fdata) - if len(entrydict) > 0: - display += " " - display += str(entrydict[cli]) - print(display) + print(fmt % tuple(fdata)) -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 = "" -modifiedentry = "" -extraentry = "" -expire = "" -singlehost = "" +def print_entries(interaction, etype): + items = getattr(interaction, etype)() + for item in items: + print("%-70s %s" % (item.entry.kind + ":" + item.entry.name, etype)) -c_list = Client.objects.all() +def main(): + parser = OptionParser(usage="%prog [options] <mode> [arg]") -result = list() -entrydict = dict() + # single host modes + multimodes = [] + singlemodes = [] + multimodes.append(make_option("-b", "--bad", action="store_true", + default=False, + help="Show bad entries from HOST")) + multimodes.append(make_option("-e", "--extra", action="store_true", + default=False, + help="Show extra entries from HOST")) + multimodes.append(make_option("-m", "--modified", action="store_true", + default=False, + help="Show modified entries from HOST")) + multimodes.append(make_option("-s", "--show", action="store_true", + default=False, + help="Equivalent to --bad --extra --modified")) + singlemodes.append(make_option("-t", "--total", action="store_true", + default=False, + help="Show total number of managed and good " + "entries from HOST")) + singlemodes.append(make_option("-x", "--expire", action="store_true", + default=False, + help="Toggle expired/unexpired state of " + "HOST")) + hostmodes = \ + OptionGroup(parser, "Single-Host Modes", + "The following mode flags require a single HOST argument") + hostmodes.add_options(multimodes) + hostmodes.add_options(singlemodes) + parser.add_option_group(hostmodes) -args = sys.argv[1:] -try: - opts, pargs = getopt.getopt(args, 'ab:cde:hm:s:t:x:', - ['stale', - 'sort=', - 'fields=', - 'badentry=', - 'modifiedentry=', - 'extraentry=']) -except getopt.GetoptError: - msg = sys.exc_info()[1] - print(msg) - print(usage) - sys.exit(2) + # all host modes + allhostmodes = OptionGroup(parser, "Host Selection Modes", + "The following mode flags require no arguments") + allhostmodes.add_option("-a", "--all", action="store_true", default=False, + help="Show all hosts, including expired hosts") + allhostmodes.add_option("-c", "--clean", action="store_true", default=False, + help="Show only clean hosts") + allhostmodes.add_option("-d", "--dirty", action="store_true", default=False, + help="Show only dirty hosts") + allhostmodes.add_option("--stale", action="store_true", default=False, + help="Show hosts that haven't run in the last 24 " + "hours") + parser.add_option_group(allhostmodes) + + # entry modes + entrymodes = \ + OptionGroup(parser, "Entry Modes", + "The following mode flags require either any number of " + "TYPE:NAME arguments describing entries, or the --file " + "option") + entrymodes.add_option("--badentry", action="store_true", default=False, + help="Show hosts that have bad entries that match " + "the argument") + entrymodes.add_option("--modifiedentry", action="store_true", default=False, + help="Show hosts that have modified entries that " + "match the argument") + entrymodes.add_option("--extraentry", action="store_true", default=False, + help="Show hosts that have extra entries that match " + "the argument") + entrymodes.add_option("--entrystatus", action="store_true", default=False, + help="Show the status of the named entry on all " + "hosts. Only supports a single entry.") + parser.add_option_group(entrymodes) + + # entry options + entryopts = OptionGroup(parser, "Entry Options", + "Options that can be used with entry modes") + entryopts.add_option("--fields", metavar="FIELD,FIELD,...", + help="Only display the listed fields", + default='name,time,state') + entryopts.add_option("--file", metavar="FILE", + help="Read TYPE:NAME pairs from the specified file " + "instead of the command line") + parser.add_option_group(entryopts) -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] == '--modifiedentry': - modifiedentry = option[1] - if option[0] == '--extraentry': - extraentry = option[1] - if option[0] == '-x': - expire = option[1] - if option[0] == '-s' or \ - option[0] == '-t' or \ - option[0] == '-b' or \ - option[0] == '-m' or \ - option[0] == '-e': - singlehost = option[1] + options, args = parser.parse_args() -if expire != "": - for c_inst in c_list: - if expire == c_inst.name: - if c_inst.expiration == None: - c_inst.expiration = datetime.datetime.now() + # make sure we've specified exactly one mode + mode_family = None + mode = None + for opt in allhostmodes.option_list + entrymodes.option_list + \ + singlemodes: + if getattr(options, opt.dest): + if mode is not None: + parser.error("Only one mode can be specified; found %s and %s" % + (mode.get_opt_string(), opt.get_opt_string())) + mode = opt + mode_family = parser.get_option_group(opt.get_opt_string()) + + # you can specify more than one of --bad, --extra, --modified, --show, so + # consider single-host options separately + if not mode_family: + for opt in multimodes: + if getattr(options, opt.dest): + mode_family = parser.get_option_group(opt.get_opt_string()) + break + + if not mode_family: + parser.error("You must specify a mode") + + if mode_family == hostmodes: + try: + cname = args.pop() + client = Client.objects.select_related().get(name=cname) + except IndexError: + parser.error("%s require a single HOST argument" % hostmodes.title) + except Client.DoesNotExist: + print("No such host: %s" % cname) + return 2 + + if options.expire: + if client.expiration == None: + client.expiration = datetime.datetime.now() print("Host expired.") else: - c_inst.expiration = None + client.expiration = None print("Host un-expired.") - c_inst.save() + client.save() + elif options.total: + managed = client.current_interaction.totalcount + good = client.current_interaction.goodcount + print("Total managed entries: %d (good: %d)" % (managed, good)) + elif mode_family == hostmodes: + if options.bad or options.show: + print_entries(client.current_interaction, "bad") -elif '-h' in args: - print(usage) -elif singlehost != "": - for c_inst in c_list: - if singlehost == c_inst.name: - if '-t' in args: - managed = c_inst.current_interaction.totalcount - good = c_inst.current_interaction.goodcount - print("Total managed entries: %d (good: %d)" % (managed, good)) - 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) - modifieditems = c_inst.current_interaction.modified() - if len(modifieditems) > 0 and ('-m' in args or '-s' in args): - print "Modified Entries:" - max_name = -1 - for item in modifieditems: - if len(item.entry.name) > max_name: - max_name = len(item.entry.name) - for item in modifieditems: - 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) - + if options.modified or options.show: + print_entries(client.current_interaction, "modified") -else: - if fields == "": - fields = ['name', 'time', 'state'] + if options.extra or options.show: + print_entries(client.current_interaction, "extra") else: - fields = fields.split(',') - - if sort != "": - sort = sort.split(',') + clients = Client.objects.exclude(current_interaction__isnull=True) + result = list() + edata = dict() + fields = options.fields.split(',') - if badentry != "": - badentry = badentry.split(',') + if mode_family == allhostmodes: + if args: + print("%s do not take any arguments, ignoring" % + allhostmodes.title) - if modifiedentry != "": - modifiedentry = modifiedentry.split(',') + for client in clients: + interaction = client.current_interaction + if (options.all or + (options.stale and interaction.isstale()) or + (options.clean and interaction.isclean()) or + (options.dirty and not interaction.isclean())): + result.append(client) + else: + # entry query modes + if options.file: + try: + entries = [l.strip().split(":") + for l in open(options.file)] + except IOError, err: + print("Cannot read entries from %s: %s" % (options.file, + err)) + return 2 + elif args: + entries = [a.split(":") for a in args] + else: + parser.error("%s require either a list of entries on the " + "command line or the --file options" % + mode_family.title) + + if options.badentry: + result = hosts_by_entry_type(clients, "bad", entries) + elif options.modifiedentry: + result = hosts_by_entry_type(clients, "modified", entries) + elif options.extraentry: + result = hosts_by_entry_type(clients, "extra", entries) + elif options.entrystatus: + if 'state' in fields: + fields.remove('state') + fields.append("entry state") + try: + entry_obj = Entries.objects.get( + kind=entries[0][0], + name=entries[0][1]) + except Entries.DoesNotExist: + print("No entry %s found" % ":".join(entries[0])) + return 2 - 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) + for client in clients: + try: + entry = \ + Entries_interactions.objects.select_related().get( + interaction=client.current_interaction, + entry=entry_obj) + edata[client] = \ + {"entry state":dict(TYPE_CHOICES)[entry.type], + "reason":entry.reason} + result.append(client) + except Entries_interactions.DoesNotExist: + pass - elif badentry != "": - if len(badentry) == 1: - fileread = fileinput.input(badentry[0]) - try: - 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.entry.name == badentry[1] and item.entry.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 - except IOError: - e = sys.exc_info()[1] - print("Cannot read %s: %s" % (e.filename, e.strerror)) - else: - for c_inst in c_list: - baditems = c_inst.current_interaction.bad() - for item in baditems: - if item.entry.name == badentry[1] and item.entry.kind == badentry[0]: - result.append(c_inst) - break - elif modifiedentry != "": - if len(modifiedentry) == 1: - fileread = fileinput.input(modifiedentry[0]) - try: - for line in fileread: - modifiedentry = line.strip().split(',') - for c_inst in c_list: - modifieditems = c_inst.current_interaction.modified() - for item in modifieditems: - if item.entry.name == modifiedentry[1] and item.entry.kind == modifiedentry[0]: - result.append(c_inst) - if c_inst in entrydict: - entrydict.get(c_inst).append(modifiedentry[1]) - else: - entrydict[c_inst] = [modifiedentry[1]] - break - except IOError: - e = sys.exc_info()[1] - print("Cannot read %s: %s" % (e.filename, e.strerror)) - else: - for c_inst in c_list: - modifieditems = c_inst.current_interaction.modified() - for item in modifieditems: - if item.entry.name == modifiedentry[1] and item.entry.kind == modifiedentry[0]: - result.append(c_inst) - break - elif extraentry != "": - if len(extraentry) == 1: - fileread = fileinput.input(extraentry[0]) - try: - 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.entry.name == extraentry[1] and item.entry.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 - except IOError: - e = sys.exc_info()[1] - print("Cannot read %s: %s" % (e.filename, e.strerror)) - else: - for c_inst in c_list: - extraitems = c_inst.current_interaction.extra() - for item in extraitems: - if item.entry.name == extraentry[1] and item.entry.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 'name' not in fields: + fields.insert(0, "name") + max_name = max(len(c.name) for c in result) + ffmt = [] + for field in fields: + if field == "name": + ffmt.append("%%-%ds" % max_name) + elif field == "time": + ffmt.append("%-19s") + else: + ffmt.append("%%-%ds" % len(field)) + fmt = " ".join(ffmt) + print(fmt % tuple(f.title() for f in fields)) + for client in result: + if not client.expiration: + print_fields(fields, client, fmt, + extra=edata.get(client, None)) - 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) +if __name__ == "__main__": + sys.exit(main()) diff --git a/src/sbin/bcfg2-server b/src/sbin/bcfg2-server index 546d5a249..d03edc93e 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 @@ -16,35 +15,11 @@ 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, - 'listen_all' : Bcfg2.Options.SERVER_LISTEN_ALL, - '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) + optinfo = dict() + optinfo.update(Bcfg2.Options.CLI_COMMON_OPTIONS) + optinfo.update(Bcfg2.Options.SERVER_COMMON_OPTIONS) + optinfo.update(Bcfg2.Options.DAEMON_COMMON_OPTIONS) + setup = Bcfg2.Options.OptionParser(optinfo) setup.parse(sys.argv[1:]) try: # check whether the specified bcfg2.conf exists @@ -54,10 +29,10 @@ if __name__ == '__main__': Bcfg2.Component.run_component(Bcfg2.Server.Core.Core, listen_all=setup['listen_all'], location=setup['location'], - daemon = setup['daemon'], - pidfile_name = setup['daemon'], - protocol = setup['protocol'], - to_file=setup['to_file'], + daemon=setup['daemon'], + pidfile_name=setup['daemon'], + protocol=setup['protocol'], + to_file=setup['logging'], cfile=setup['configfile'], register=False, cls_kwargs={'repo':setup['repo'], @@ -65,11 +40,12 @@ if __name__ == '__main__': 'password':setup['password'], 'encoding':setup['encoding'], 'ca':setup['ca'], - 'filemonitor':setup['fm'], - 'start_fam_thread':True}, + 'filemonitor':setup['filemonitor'], + 'start_fam_thread':True, + 'setup':setup}, keyfile=setup['key'], certfile=setup['cert'], - ca=setup['ca'], + ca=setup['ca'] ) except CoreInitError: msg = sys.exc_info()[1] diff --git a/src/sbin/bcfg2-test b/src/sbin/bcfg2-test index 01a2a4893..653c24124 100755 --- a/src/sbin/bcfg2-test +++ b/src/sbin/bcfg2-test @@ -61,17 +61,11 @@ class ClientTest(TestCase): id = __str__ 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, - 'verbose': Bcfg2.Options.VERBOSE, - 'noseopts': Bcfg2.Options.TEST_NOSEOPTS, - 'ignore': Bcfg2.Options.TEST_IGNORE, - } + optinfo = dict(noseopts=Bcfg2.Options.TEST_NOSEOPTS, + test_ignore=Bcfg2.Options.TEST_IGNORE, + validate=Bcfg2.Options.CFG_VALIDATION) + optinfo.update(Bcfg2.Options.CLI_COMMON_OPTIONS) + optinfo.update(Bcfg2.Options.SERVER_COMMON_OPTIONS) setup = Bcfg2.Options.OptionParser(optinfo) setup.hm = \ "bcfg2-test [options] [client] [client] [...]\nOptions:\n %s" % \ @@ -86,11 +80,12 @@ def main(): setup['plugins'], setup['password'], setup['encoding'], - filemonitor='pseudo' + filemonitor=setup['filemonitor'], + setup=setup ) ignore = dict() - for entry in setup['ignore']: + for entry in setup['test_ignore']: tag, name = entry.split(":") try: ignore[tag].append(name) diff --git a/src/sbin/bcfg2-yum-helper b/src/sbin/bcfg2-yum-helper index dc46bb81a..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 |