From 04e7e0c9e9f96b4ba8bdb349cc0a37d9a881a4d2 Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Wed, 3 Oct 2012 11:35:05 -0400 Subject: testsuite: expanded pylint coverage --- src/lib/Bcfg2/Server/Plugins/Packages/__init__.py | 18 +- src/lib/Bcfg2/Server/Plugins/SSHbase.py | 88 +++-- src/lib/Bcfg2/Server/Plugins/SSLCA.py | 85 ++--- src/sbin/bcfg2-info | 430 +++++++++++----------- testsuite/Testsrc/test_code_checks.py | 7 +- testsuite/pylintrc.conf | 2 +- 6 files changed, 322 insertions(+), 308 deletions(-) diff --git a/src/lib/Bcfg2/Server/Plugins/Packages/__init__.py b/src/lib/Bcfg2/Server/Plugins/Packages/__init__.py index 628ba929f..c8f643415 100644 --- a/src/lib/Bcfg2/Server/Plugins/Packages/__init__.py +++ b/src/lib/Bcfg2/Server/Plugins/Packages/__init__.py @@ -34,7 +34,7 @@ class Packages(Bcfg2.Server.Plugin.Plugin, or defer to package manager libraries for truly dynamic resolution. - .. private-include: _build_packages, _get_collection""" + .. private-include: _build_packages""" #: Packages is an alternative to #: :mod:`Bcfg2.Server.Plugins.Pkgmgr` and conflicts with it. @@ -177,7 +177,7 @@ class Packages(Bcfg2.Server.Plugin.Plugin, perms='0644', important='true') - collection = self._get_collection(metadata) + collection = self.get_collection(metadata) entry.text = collection.get_config() for (key, value) in list(attrib.items()): entry.attrib.__setitem__(key, value) @@ -199,7 +199,7 @@ class Packages(Bcfg2.Server.Plugin.Plugin, :return: lxml.etree._Element - The fully bound entry """ if entry.tag == 'Package': - collection = self._get_collection(metadata) + collection = self.get_collection(metadata) entry.set('version', self.core.setup.cfp.get("packages", "version", default="auto")) @@ -230,7 +230,7 @@ class Packages(Bcfg2.Server.Plugin.Plugin, if entry.tag == 'Package': if self.core.setup.cfp.getboolean("packages", "magic_groups", default=False): - collection = self._get_collection(metadata) + collection = self.get_collection(metadata) if collection.magic_groups_match(): return True else: @@ -275,7 +275,7 @@ class Packages(Bcfg2.Server.Plugin.Plugin, :type structures: list of lxml.etree._Element objects :returns: None """ - collection = self._get_collection(metadata) + collection = self.get_collection(metadata) indep = lxml.etree.Element('Independent') self._build_packages(metadata, indep, structures, collection=collection) @@ -301,7 +301,7 @@ class Packages(Bcfg2.Server.Plugin.Plugin, :type structures: list of lxml.etree._Element objects :param collection: The collection of sources for this client. If none is given, one will be created with - :func:`_get_collection` + :func:`get_collection` :type collection: Bcfg2.Server.Plugins.Packages.Collection.Collection """ if self.disableResolver: @@ -309,7 +309,7 @@ class Packages(Bcfg2.Server.Plugin.Plugin, return if collection is None: - collection = self._get_collection(metadata) + collection = self.get_collection(metadata) # initial is the set of packages that are explicitly specified # in the configuration initial = set() @@ -442,7 +442,7 @@ class Packages(Bcfg2.Server.Plugin.Plugin, if kfile not in keyfiles: os.unlink(kfile) - def _get_collection(self, metadata): + def get_collection(self, metadata): """ Get a :class:`Bcfg2.Server.Plugins.Packages.Collection.Collection` object for this client. @@ -508,7 +508,7 @@ class Packages(Bcfg2.Server.Plugin.Plugin, :type metadata: Bcfg2.Server.Plugins.Metadata.ClientMetadata :return: dict of lists of ``url_map`` data """ - collection = self._get_collection(metadata) + collection = self.get_collection(metadata) return dict(sources=collection.get_additional_data()) def end_client_run(self, metadata): diff --git a/src/lib/Bcfg2/Server/Plugins/SSHbase.py b/src/lib/Bcfg2/Server/Plugins/SSHbase.py index 1d5fb87c2..0d6b47807 100644 --- a/src/lib/Bcfg2/Server/Plugins/SSHbase.py +++ b/src/lib/Bcfg2/Server/Plugins/SSHbase.py @@ -9,11 +9,14 @@ import logging import tempfile from subprocess import Popen, PIPE import Bcfg2.Server.Plugin -from Bcfg2.Compat import u_str, reduce, b64encode +from Bcfg2.Compat import u_str, reduce, b64encode # pylint: disable=W0622 + +LOGGER = logging.getLogger(__name__) -logger = logging.getLogger(__name__) class KeyData(Bcfg2.Server.Plugin.SpecificData): + """ class to handle key data for HostKeyEntrySet """ + def __init__(self, name, specific, encoding): Bcfg2.Server.Plugin.SpecificData.__init__(self, name, @@ -24,7 +27,14 @@ class KeyData(Bcfg2.Server.Plugin.SpecificData): def __lt__(self, other): return self.name < other.name - def bind_entry(self, entry, metadata): + def bind_entry(self, entry, _): + """ Bind the entry with the data of this key + + :param entry: The abstract entry to bind. This will be + modified in place. + :type entry: lxml.etree._Element + :returns: None + """ entry.set('type', 'file') if entry.get('encoding') == 'base64': entry.text = b64encode(self.data) @@ -32,22 +42,24 @@ class KeyData(Bcfg2.Server.Plugin.SpecificData): try: entry.text = u_str(self.data, self.encoding) except UnicodeDecodeError: - e = sys.exc_info()[1] - logger.error("Failed to decode %s: %s" % (entry.get('name'), e)) - logger.error("Please verify you are using the proper encoding.") - raise Bcfg2.Server.Plugin.PluginExecutionError + msg = "Failed to decode %s: %s" % (entry.get('name'), + sys.exc_info()[1]) + LOGGER.error(msg) + LOGGER.error("Please verify you are using the proper encoding") + raise Bcfg2.Server.Plugin.PluginExecutionError(msg) except ValueError: - e = sys.exc_info()[1] - logger.error("Error in specification for %s" % + msg = "Error in specification for %s: %s" % (entry.get('name'), + sys.exc_info()[1]) + LOGGER.error(msg) + LOGGER.error("You need to specify base64 encoding for %s" % entry.get('name')) - logger.error(str(e)) - logger.error("You need to specify base64 encoding for %s." % - entry.get('name')) - raise Bcfg2.Server.Plugin.PluginExecutionError + raise Bcfg2.Server.Plugin.PluginExecutionError(msg) if entry.text in ['', None]: entry.set('empty', 'true') + class HostKeyEntrySet(Bcfg2.Server.Plugin.EntrySet): + """ EntrySet to handle all kinds of host keys """ def __init__(self, basename, path): if basename.startswith("ssh_host_key"): encoding = "base64" @@ -67,6 +79,7 @@ class HostKeyEntrySet(Bcfg2.Server.Plugin.EntrySet): class KnownHostsEntrySet(Bcfg2.Server.Plugin.EntrySet): + """ EntrySet to handle the ssh_known_hosts file """ def __init__(self, path): Bcfg2.Server.Plugin.EntrySet.__init__(self, "ssh_known_hosts", path, KeyData, None) @@ -128,11 +141,12 @@ class SSHbase(Bcfg2.Server.Plugin.Plugin, self.entries = dict() self.Entries['Path'] = dict() - self.entries['/etc/ssh/ssh_known_hosts'] = KnownHostsEntrySet(self.data) + self.entries['/etc/ssh/ssh_known_hosts'] = \ + KnownHostsEntrySet(self.data) self.Entries['Path']['/etc/ssh/ssh_known_hosts'] = self.build_skn for keypattern in self.keypatterns: - self.entries["/etc/ssh/" + keypattern] = HostKeyEntrySet(keypattern, - self.data) + self.entries["/etc/ssh/" + keypattern] = \ + HostKeyEntrySet(keypattern, self.data) self.Entries['Path']["/etc/ssh/" + keypattern] = self.build_hk def get_skn(self): @@ -159,17 +173,19 @@ class SSHbase(Bcfg2.Server.Plugin.Plugin, newnames.add(name.split('.')[0]) try: newips.add(self.get_ipcache_entry(name)[0]) - except: + except: # pylint: disable=W0702 continue names[cmeta.hostname].update(newnames) names[cmeta.hostname].update(cmeta.addresses) names[cmeta.hostname].update(newips) - # TODO: Only perform reverse lookups on IPs if an option is set. + # TODO: Only perform reverse lookups on IPs if an + # option is set. if True: for ip in newips: try: - names[cmeta.hostname].update(self.get_namecache_entry(ip)) - except: + names[cmeta.hostname].update( + self.get_namecache_entry(ip)) + except: # pylint: disable=W0702 continue names[cmeta.hostname] = sorted(names[cmeta.hostname]) @@ -178,7 +194,8 @@ class SSHbase(Bcfg2.Server.Plugin.Plugin, pubkeys.sort() for pubkey in pubkeys: for entry in sorted(self.entries[pubkey].entries.values(), - key=lambda e: e.specific.hostname or e.specific.group): + key=lambda e: (e.specific.hostname or + e.specific.group)): specific = entry.specific hostnames = [] if specific.hostname and specific.hostname in names: @@ -251,9 +268,8 @@ class SSHbase(Bcfg2.Server.Plugin.Plugin, del self.static[event.filename] self.skn = False else: - self.static[event.filename] = \ - Bcfg2.Server.Plugin.FileBacked(os.path.join(self.data, - event.filename)) + self.static[event.filename] = Bcfg2.Server.Plugin.FileBacked( + os.path.join(self.data, event.filename)) self.static[event.filename].HandleEvent(event) self.skn = False return @@ -304,28 +320,29 @@ class SSHbase(Bcfg2.Server.Plugin.Plugin, return self.namecache[cip] except socket.gaierror: self.namecache[cip] = False - self.logger.error("Failed to find any names associated with IP address %s" % cip) + self.logger.error("Failed to find any names associated with " + "IP address %s" % cip) raise def build_skn(self, entry, metadata): """This function builds builds a host specific known_hosts file.""" try: - rv = self.entries[entry.get('name')].bind_entry(entry, metadata) + self.entries[entry.get('name')].bind_entry(entry, metadata) except Bcfg2.Server.Plugin.PluginExecutionError: - client = metadata.hostname entry.text = self.skn hostkeys = [] - for k in self.keypatterns: - if k.endswith(".pub"): + for key in self.keypatterns: + if key.endswith(".pub"): try: - hostkeys.append(self.entries["/etc/ssh/" + - k].best_matching(metadata)) + hostkeys.append( + self.entries["/etc/ssh/" + + key].best_matching(metadata)) except Bcfg2.Server.Plugin.PluginExecutionError: pass hostkeys.sort() for hostkey in hostkeys: - entry.text += "localhost,localhost.localdomain,127.0.0.1 %s" % \ - (hostkey.data) + entry.text += "localhost,localhost.localdomain,127.0.0.1 %s" \ + % hostkey.data self.entries[entry.get('name')].bind_info_to_entry(entry, metadata) def build_hk(self, entry, metadata): @@ -410,5 +427,6 @@ class SSHbase(Bcfg2.Server.Plugin.Plugin, if log: print("Wrote file %s" % filename) except KeyError: - self.logger.error("Failed to pull %s. This file does not currently " - "exist on the client" % entry.get('name')) + self.logger.error("Failed to pull %s. This file does not " + "currently exist on the client" % + entry.get('name')) diff --git a/src/lib/Bcfg2/Server/Plugins/SSLCA.py b/src/lib/Bcfg2/Server/Plugins/SSLCA.py index 8ca95ff62..666f27e53 100644 --- a/src/lib/Bcfg2/Server/Plugins/SSLCA.py +++ b/src/lib/Bcfg2/Server/Plugins/SSLCA.py @@ -1,7 +1,9 @@ +""" The SSLCA generator handles the creation and management of ssl +certificates and their keys. """ + import Bcfg2.Server.Plugin import Bcfg2.Options import lxml.etree -import posixpath import tempfile import os from subprocess import Popen, PIPE, STDOUT @@ -9,11 +11,8 @@ from Bcfg2.Compat import ConfigParser, md5 class SSLCA(Bcfg2.Server.Plugin.GroupSpool): - """ - The SSLCA generator handles the creation and - management of ssl certificates and their keys. - """ - name = 'SSLCA' + """ The SSLCA generator handles the creation and management of ssl + certificates and their keys. """ __author__ = 'g.hagger@gmail.com' __child__ = Bcfg2.Server.Plugin.FileBacked key_specs = {} @@ -34,7 +33,7 @@ class SSLCA(Bcfg2.Server.Plugin.GroupSpool): return epath = "".join([self.data, self.handles[event.requestID], event.filename]) - if posixpath.isdir(epath): + if os.path.isdir(epath): ident = self.handles[event.requestID] + event.filename else: ident = self.handles[event.requestID][:-1] @@ -44,16 +43,20 @@ class SSLCA(Bcfg2.Server.Plugin.GroupSpool): if event.filename.endswith('.xml'): if action in ['exists', 'created', 'changed']: if event.filename.endswith('key.xml'): - key_spec = dict(list(lxml.etree.parse(epath, - parser=Bcfg2.Server.XMLParser).find('Key').items())) + key_spec = dict(list(lxml.etree.parse( + epath, + parser=Bcfg2.Server.XMLParser + ).find('Key').items())) self.key_specs[ident] = { 'bits': key_spec.get('bits', 2048), 'type': key_spec.get('type', 'rsa') } self.Entries['Path'][ident] = self.get_key elif event.filename.endswith('cert.xml'): - cert_spec = dict(list(lxml.etree.parse(epath, - parser=Bcfg2.Server.XMLParser).find('Cert').items())) + cert_spec = dict(list(lxml.etree.parse( + epath, + parser=Bcfg2.Server.XMLParser + ).find('Cert').items())) ca = cert_spec.get('ca', 'default') self.cert_specs[ident] = { 'ca': ca, @@ -67,9 +70,9 @@ class SSLCA(Bcfg2.Server.Plugin.GroupSpool): 'O': cert_spec.get('o'), 'emailAddress': cert_spec.get('emailaddress') } - cp = ConfigParser.ConfigParser() - cp.read(self.core.cfile) - self.CAs[ca] = dict(cp.items('sslca_' + ca)) + cfp = ConfigParser.ConfigParser() + cfp.read(self.core.cfile) + self.CAs[ca] = dict(cfp.items('sslca_' + ca)) self.Entries['Path'][ident] = self.get_cert elif event.filename.endswith("info.xml"): self.infoxml[ident] = Bcfg2.Server.Plugin.InfoXML(epath) @@ -79,9 +82,9 @@ class SSLCA(Bcfg2.Server.Plugin.GroupSpool): del self.Entries['Path'][ident] else: if action in ['exists', 'created']: - if posixpath.isdir(epath): + if os.path.isdir(epath): self.AddDirectoryMonitor(epath[len(self.data):]) - if ident not in self.entries and posixpath.isfile(epath): + if ident not in self.entries and os.path.isfile(epath): self.entries[fname] = self.__child__(epath) self.entries[fname].HandleEvent(event) if action == 'changed': @@ -103,7 +106,7 @@ class SSLCA(Bcfg2.Server.Plugin.GroupSpool): filename = os.path.join(path, "%s.H_%s" % (os.path.basename(path), metadata.hostname)) if filename not in list(self.entries.keys()): - key = self.build_key(filename, entry, metadata) + key = self.build_key(entry) open(self.data + filename, 'w').write(key) entry.text = key self.entries[filename] = self.__child__(self.data + filename) @@ -118,18 +121,15 @@ class SSLCA(Bcfg2.Server.Plugin.GroupSpool): else: Bcfg2.Server.Plugin.bind_info(entry, metadata) - def build_key(self, filename, entry, metadata): - """ - generates a new key according the the specification - """ - type = self.key_specs[entry.get('name')]['type'] + def build_key(self, entry): + """ generates a new key according the the specification """ + ktype = self.key_specs[entry.get('name')]['type'] bits = self.key_specs[entry.get('name')]['bits'] - if type == 'rsa': + if ktype == 'rsa': cmd = ["openssl", "genrsa", bits] - elif type == 'dsa': + elif ktype == 'dsa': cmd = ["openssl", "dsaparam", "-noout", "-genkey", bits] - key = Popen(cmd, stdout=PIPE).stdout.read() - return key + return Popen(cmd, stdout=PIPE).stdout.read() def get_cert(self, entry, metadata): """ @@ -145,12 +145,12 @@ class SSLCA(Bcfg2.Server.Plugin.GroupSpool): key_filename = os.path.join(key, "%s.H_%s" % (os.path.basename(key), metadata.hostname)) if key_filename not in self.entries: - e = lxml.etree.Element('Path') - e.set('name', key) - self.core.Bind(e, metadata) + el = lxml.etree.Element('Path') + el.set('name', key) + self.core.Bind(el, metadata) # check if we have a valid hostfile - if (filename in list(self.entries.keys()) and + if (filename in list(self.entries.keys()) and self.verify_cert(filename, key_filename, entry)): entry.text = self.entries[filename].data else: @@ -168,6 +168,8 @@ class SSLCA(Bcfg2.Server.Plugin.GroupSpool): Bcfg2.Server.Plugin.bind_info(entry, metadata) def verify_cert(self, filename, key_filename, entry): + """ Perform certification verification against the CA and + against the key """ ca = self.CAs[self.cert_specs[entry.get('name')]['ca']] do_verify = ca.get('chaincert') if do_verify: @@ -253,8 +255,8 @@ class SSLCA(Bcfg2.Server.Plugin.GroupSpool): """ # create temp request config file conffile = open(tempfile.mkstemp()[1], 'w') - cp = ConfigParser.ConfigParser({}) - cp.optionxform = str + cfp = ConfigParser.ConfigParser({}) + cfp.optionxform = str defaults = { 'req': { 'default_md': 'sha1', @@ -270,20 +272,21 @@ class SSLCA(Bcfg2.Server.Plugin.GroupSpool): 'alt_names': {} } for section in list(defaults.keys()): - cp.add_section(section) + cfp.add_section(section) for key in defaults[section]: - cp.set(section, key, defaults[section][key]) - x = 1 + cfp.set(section, key, defaults[section][key]) + altnamenum = 1 altnames = list(metadata.aliases) altnames.append(metadata.hostname) for altname in altnames: - cp.set('alt_names', 'DNS.' + str(x), altname) - x += 1 + cfp.set('alt_names', 'DNS.' + str(altnamenum), altname) + altnamenum += 1 for item in ['C', 'L', 'ST', 'O', 'OU', 'emailAddress']: if self.cert_specs[entry.get('name')][item]: - cp.set('req_distinguished_name', item, self.cert_specs[entry.get('name')][item]) - cp.set('req_distinguished_name', 'CN', metadata.hostname) - cp.write(conffile) + cfp.set('req_distinguished_name', item, + self.cert_specs[entry.get('name')][item]) + cfp.set('req_distinguished_name', 'CN', metadata.hostname) + cfp.write(conffile) conffile.close() return conffile.name @@ -296,5 +299,5 @@ class SSLCA(Bcfg2.Server.Plugin.GroupSpool): key = self.data + key_filename cmd = ["openssl", "req", "-new", "-config", req_config, "-days", days, "-key", key, "-text", "-out", req] - res = Popen(cmd, stdout=PIPE).stdout.read() + Popen(cmd, stdout=PIPE).wait() return req diff --git a/src/sbin/bcfg2-info b/src/sbin/bcfg2-info index 3d1e8bc76..d16048df0 100755 --- a/src/sbin/bcfg2-info +++ b/src/sbin/bcfg2-info @@ -2,106 +2,71 @@ """This tool loads the Bcfg2 core into an interactive debugger.""" import os +import re import sys import cmd -import errno import getopt import fnmatch -import logging -import tempfile import lxml.etree import traceback from code import InteractiveConsole +import Bcfg2.Logger +import Bcfg2.Options +import Bcfg2.Server.Core +import Bcfg2.Server.Plugin +import Bcfg2.Client.Tools.POSIX try: try: import cProfile as profile - except: + except ImportError: import profile import pstats - have_profile = True -except: - have_profile = False - -import Bcfg2.Logger -import Bcfg2.Options -import Bcfg2.Server.Core -import Bcfg2.Server.Plugin + HAS_PROFILE = True +except ImportError: + HAS_PROFILE = False try: from Bcfg2.Server.Plugins.Bundler import BundleTemplateFile - has_genshi = True + HAS_GENSHI = True except ImportError: - has_genshi = False - -logger = logging.getLogger('bcfg2-info') -USAGE = """Commands: -build - Build config for hostname, writing to filename -builddir - Build config for hostname, writing separate files to dirname -buildall [] - Build configs for all clients in directory -buildallfile [] - Build config file for all clients in directory -buildfile - Build config file for hostname (not written to disk) -buildbundle - Render a templated bundle for hostname (not written to disk) -automatch - Perform automatch on a Properties file -bundles - Print out group/bundle information -clients - Print out client/profile information -config - Print out the configuration of the Bcfg2 server -debug - Shell out to native python interpreter -event_debug - Display filesystem events as they are processed -groups - List groups -help - Print this list of available commands -mappings - Print generator mappings for optional type and name -packageresolve [...] - Resolve the specified set of packages -packagesources - Show package sources -profile - Profile a single bcfg2-info command -quit - Exit the bcfg2-info command line -showentries - Show abstract configuration entries for a given host -showclient - Show metadata for given hosts -update - Process pending file events""" - -BUILDDIR_USAGE = """Usage: builddir [-f] + HAS_GENSHI = False -Generates a config for client and writes the -individual configuration files out separately in a tree -under . The directory must be -rooted under /tmp unless the -f argument is provided, in -which case it can be located anywhere. -NOTE: Currently only handles file entries and writes -all content with the default owner and permissions. These -could be much more permissive than would be created by the -Bcfg2 client itself.""" - - -class mockLog(object): +class MockLog(object): + """ Fake logger that just discards all messages in order to mask + errors from builddir being unable to chown files it creates """ def error(self, *args, **kwargs): + """ discard error messages """ + pass + + def warning(self, *args, **kwargs): + """ discard warning messages """ pass def info(self, *args, **kwargs): + """ discard info messages """ pass def debug(self, *args, **kwargs): + """ discard debug messages """ pass -class dummyError(Exception): - """This is just a dummy.""" - 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): +def print_tabular(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]))]) + 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]) @@ -109,17 +74,22 @@ def printTabular(rows): for row in rows[1:]: print(fstring % row) -def displayTrace(trace, num=80, sort=('time', 'calls')): + +def display_trace(trace): + """ display statistics from a profile trace """ stats = pstats.Stats(trace) stats.sort_stats('cumulative', 'calls', 'time') stats.print_stats(200) + def load_interpreters(): + """ Load a dict of available Python interpreters """ interpreters = dict(python=lambda v: InteractiveConsole(v).interact()) best = "python" try: import bpython.cli - interpreters["bpython"] = lambda v: bpython.cli.main(args=[], locals_=v) + interpreters["bpython"] = lambda v: bpython.cli.main(args=[], + locals_=v) best = "bpython" except ImportError: pass @@ -148,11 +118,15 @@ def load_interpreters(): return interpreters -class infoCore(cmd.Cmd, Bcfg2.Server.Core.BaseCore): +class InfoCore(cmd.Cmd, Bcfg2.Server.Core.BaseCore): """Main class for bcfg2-info.""" def __init__(self, setup): cmd.Cmd.__init__(self) + saved = (setup['syslog'], setup['logging']) + setup['syslog'] = False + setup['logging'] = None Bcfg2.Server.Core.BaseCore.__init__(self, setup=setup) + setup['syslog'], setup['logging'] = saved self.prompt = '> ' self.cont = True self.fam.handle_events_in_interval(4) @@ -180,6 +154,10 @@ class infoCore(cmd.Cmd, Bcfg2.Server.Core.BaseCore): clist.difference_update(rv) return list(rv) + def _get_usage(self, func): + """ get the short usage message for a given function """ + return "Usage: " + re.sub(r'\s+', ' ', func.__doc__).split(" - ", 1)[0] + def do_loop(self): """Looping.""" self.cont = True @@ -194,17 +172,17 @@ class infoCore(cmd.Cmd, Bcfg2.Server.Core.BaseCore): except KeyboardInterrupt: print("Ctrl-C pressed exiting...") self.do_exit([]) - except dummyError: - continue except: - logger.error("Command failure", exc_info=1) + self.logger.error("Command failure", exc_info=1) def do_debug(self, args): - """Debugging mode for more details.""" + """ debug [-n] [-f ] - Shell out to native + python interpreter """ try: opts, _ = getopt.getopt(args.split(), 'nf:') - except: - print("Usage: debug [-n] [-f ]") + except getopt.GetoptError: + print(str(sys.exc_info()[1])) + print(self._get_usage(self.do_debug)) return self.cont = False scriptmode = False @@ -216,43 +194,42 @@ class infoCore(cmd.Cmd, Bcfg2.Server.Core.BaseCore): elif opt[0] == '-n': interactive = False if scriptmode: - sh = InteractiveConsole(locals()) + console = InteractiveConsole(locals()) for command in [c.strip() for c in open(spath).readlines()]: if command: - sh.push(command) + console.push(command) if interactive: interpreters = load_interpreters() - if setup['interpreter'] in interpreters: + if self.setup['interpreter'] in interpreters: print("Dropping to %s interpreter; press ^D to resume" % - setup['interpreter']) - interpreters[setup['interpreter']](locals()) + self.setup['interpreter']) + interpreters[self.setup['interpreter']](locals()) else: - logger.error("Invalid interpreter %s" % setup['interpreter']) - logger.error("Valid interpreters are: %s" % - ", ".join(interpreters.keys())) + self.logger.error("Invalid interpreter %s" % + self.setup['interpreter']) + self.logger.error("Valid interpreters are: %s" % + ", ".join(interpreters.keys())) def do_quit(self, _): - """ - Exit program. - Usage: [quit|exit] - """ + """ quit|exit - Exit program """ for plugin in list(self.plugins.values()): plugin.shutdown() - os._exit(0) + os._exit(0) # pylint: disable=W0212 do_EOF = do_quit do_exit = do_quit def do_help(self, _): - """Print out usage info.""" + """ help - Print this list of available commands """ print(USAGE) def do_update(self, _): - """Process pending filesystem events.""" + """ update - Process pending filesystem events""" self.fam.handle_events_in_interval(0.1) def do_build(self, args): - """Build client configuration.""" + """ build [-f] - Build config for + hostname, writing to filename""" alist = args.split() path_force = False for arg in alist: @@ -262,20 +239,34 @@ class infoCore(cmd.Cmd, Bcfg2.Server.Core.BaseCore): 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") + print("Refusing to write files outside of /tmp without -f " + "option") return - lxml.etree.ElementTree(self.BuildConfiguration(client)).write(ofile, - encoding='UTF-8', xml_declaration=True, - pretty_print=True) + lxml.etree.ElementTree(self.BuildConfiguration(client)).write( + ofile, + encoding='UTF-8', xml_declaration=True, + pretty_print=True) else: - print('Usage: build [-f] ') + print(self._get_usage(self.do_build)) def help_builddir(self): """Display help for builddir command.""" - print(BUILDDIR_USAGE) + print("""Usage: builddir [-f] + +Generates a config for client and writes the +individual configuration files out separately in a tree +under . The directory must be +rooted under /tmp unless the -f argument is provided, in +which case it can be located anywhere. + +NOTE: Currently only handles file entries and writes +all content with the default owner and permissions. These +could be much more permissive than would be created by the +Bcfg2 client itself.""") def do_builddir(self, args): - """Build client configuration as separate files within a dir.""" + """ builddir [-f] - Build config for + hostname, writing separate files to dirname""" alist = args.split() path_force = False if '-f' in args: @@ -284,7 +275,8 @@ class infoCore(cmd.Cmd, Bcfg2.Server.Core.BaseCore): 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") + print("Refusing to write files outside of /tmp without -f " + "option") return client_config = self.BuildConfiguration(client) if client_config.tag == 'error': @@ -296,20 +288,22 @@ class infoCore(cmd.Cmd, Bcfg2.Server.Core.BaseCore): if entry.tag == 'Path': entry.set('name', odir + '/' + entry.get('name')) - log = mockLog() - import Bcfg2.Client.Tools.POSIX - p = Bcfg2.Client.Tools.POSIX.POSIX(log, setup, client_config) + posix = Bcfg2.Client.Tools.POSIX.POSIX(MockLog(), + self.setup, + client_config) states = dict() - p.Inventory(states) - p.Install(list(states.keys()), states) + posix.Inventory(states) + posix.Install(list(states.keys()), states) else: print('Error: Incorrect number of parameters.') self.help_builddir() def do_buildall(self, args): + """ buildall [] - Build configs for + all clients in directory """ alist = args.split() if len(alist) < 1: - print("Usage: buildall []") + print(self._get_usage(self.do_buildall)) return destdir = alist[0] @@ -328,19 +322,20 @@ class infoCore(cmd.Cmd, Bcfg2.Server.Core.BaseCore): client + ".xml"))) def do_buildallfile(self, args): - """Build a config file for all clients.""" - usage = 'Usage: buildallfile [--altsrc=] []' + """ buildallfile [] - Build + config file for all clients in directory """ try: opts, args = getopt.gnu_getopt(args.split(), '', ['altsrc=']) - except: - print(usage) + except getopt.GetoptError: + print(str(sys.exc_info()[1])) + print(self._get_usage(self.do_buildallfile)) return altsrc = None for opt in opts: if opt[0] == '--altsrc': altsrc = opt[1] if len(args) < 2: - print(usage) + print(self._get_usage(self.do_buildallfile)) return destdir = args[0] @@ -364,12 +359,14 @@ class infoCore(cmd.Cmd, Bcfg2.Server.Core.BaseCore): filename, client)) def do_buildfile(self, args): - """Build a config file for client.""" - usage = 'Usage: buildfile [-f ] [--altsrc=] filename hostname' + """ buildfile [-f ] [--altsrc=] + - Build config file for hostname (not written to + disk)""" try: opts, alist = getopt.gnu_getopt(args.split(), 'f:', ['altsrc=']) - except: - print(usage) + except getopt.GetoptError: + print(str(sys.exc_info()[1])) + print(self.do_buildfile.__doc__) return altsrc = None outfile = None @@ -379,7 +376,7 @@ class infoCore(cmd.Cmd, Bcfg2.Server.Core.BaseCore): elif opt[0] == '-f': outfile = opt[1] if len(alist) != 2: - print(usage) + print(self.do_buildfile.__doc__) return fname, client = alist @@ -406,14 +403,15 @@ class infoCore(cmd.Cmd, Bcfg2.Server.Core.BaseCore): print(data) def do_buildbundle(self, args): - """Render a bundle for client.""" + """ buildbundle - Render a templated + bundle for hostname (not written to disk) """ if len(args.split()) == 2: bname, client = args.split() try: metadata = self.build_metadata(client) if bname in self.plugins['Bundler'].entries: bundle = self.plugins['Bundler'].entries[bname] - if (has_genshi and + if (HAS_GENSHI and isinstance(bundle, BundleTemplateFile)): stream = bundle.template.generate(metadata=metadata) @@ -422,15 +420,17 @@ class infoCore(cmd.Cmd, Bcfg2.Server.Core.BaseCore): print(bundle.data) else: print("No such bundle %s" % bname) - except: + except: # pylint: disable=W0702 err = sys.exc_info()[1] print("Failed to render bundle %s for host %s: %s" % (bname, client, err)) else: - print('Usage: buildbundle filename hostname') + print(self._get_usage(self.do_buildbundle)) def do_automatch(self, args): + """ automatch [-f] - Perform automatch on + a Properties file """ alist = args.split() force = False for arg in alist: @@ -438,7 +438,7 @@ class infoCore(cmd.Cmd, Bcfg2.Server.Core.BaseCore): alist.remove('-f') force = True if len(alist) != 2: - print("Usage: automatch [-f] ") + print(self._get_usage(self.do_automatch)) return if 'Properties' not in self.plugins: @@ -446,70 +446,63 @@ class infoCore(cmd.Cmd, Bcfg2.Server.Core.BaseCore): return pname, client = alist - try: - automatch = self.setup.cfp.getboolean("properties", - "automatch", - default=False) - - pfile = self.plugins['Properties'].store.entries[pname] - if (not force and - not automatch and - pfile.xdata.get("automatch", "false").lower() != "true"): - print("Automatch not enabled on %s" % pname) - else: - metadata = self.build_metadata(client) - print(lxml.etree.tostring(pfile.XMLMatch(metadata), - xml_declaration=False, - pretty_print=True).decode('UTF-8')) - except: - err = sys.exc_info()[1] - print("Failed to automatch %s for host %s: %s" % (pname, - client, - err)) + automatch = self.setup.cfp.getboolean("properties", "automatch", + default=False) + pfile = self.plugins['Properties'].store.entries[pname] + if (not force and + not automatch and + pfile.xdata.get("automatch", "false").lower() != "true"): + print("Automatch not enabled on %s" % pname) + else: + metadata = self.build_metadata(client) + print(lxml.etree.tostring(pfile.XMLMatch(metadata), + xml_declaration=False, + pretty_print=True).decode('UTF-8')) def do_bundles(self, _): - """Print out group/bundle info.""" + """ bundles - 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) + print_tabular(data) def do_clients(self, _): - """Print out client info.""" + """ clients - Print out client/profile info """ data = [('Client', 'Profile')] clist = self.metadata.clients clist.sort() for client in clist: imd = self.metadata.get_initial_metadata(client) data.append((client, imd.profile)) - printTabular(data) + print_tabular(data) def do_config(self, _): - """Print out the current configuration of Bcfg2.""" + """ config - Print out the current configuration of Bcfg2""" output = [ ('Description', 'Value'), - ('Path Bcfg2 repository', setup['repo']), - ('Plugins', setup['plugins']), - ('Password', setup['password']), - ('Server Metadata Connector', setup['mconnect']), - ('Filemonitor', setup['filemonitor']), - ('Server address', setup['location']), - ('Path to key', setup['key']), - ('Path to SSL certificate', setup['cert']), - ('Path to SSL CA certificate', setup['ca']), - ('Protocol', setup['protocol']), - ('Logging', setup['logging']) + ('Path Bcfg2 repository', self.setup['repo']), + ('Plugins', self.setup['plugins']), + ('Password', self.setup['password']), + ('Server Metadata Connector', self.setup['mconnect']), + ('Filemonitor', self.setup['filemonitor']), + ('Server address', self.setup['location']), + ('Path to key', self.setup['key']), + ('Path to SSL certificate', self.setup['cert']), + ('Path to SSL CA certificate', self.setup['ca']), + ('Protocol', self.setup['protocol']), + ('Logging', self.setup['logging']) ] - printTabular(output) + print_tabular(output) def do_showentries(self, args): - """Show abstract configuration entries for a given host.""" + """ showentries - Show abstract + configuration entries for a given host """ arglen = len(args.split()) if arglen not in [1, 2]: - print("Usage: showentries ") + print(self._get_usage(self.do_showentries)) return client = args.split()[0] try: @@ -529,12 +522,10 @@ class infoCore(cmd.Cmd, Bcfg2.Server.Core.BaseCore): for child in item.getchildren(): if child.tag in [etype, "Bound%s" % etype]: output.append((child.tag, child.get('name'))) - printTabular(output) + print_tabular(output) def do_groups(self, _): - """Print out group info.""" - # FIXME: Contains doesn't work. Not sure what it was used for - #data = [("Groups", "Profile", "Category", "Contains")] + """ groups - Print out group info """ data = [("Groups", "Profile", "Category")] grouplist = list(self.metadata.groups.keys()) grouplist.sort() @@ -545,18 +536,18 @@ class infoCore(cmd.Cmd, Bcfg2.Server.Core.BaseCore): prof = 'no' cat = self.metadata.groups[group].category data.append((group, prof, cat)) - printTabular(data) + print_tabular(data) def do_showclient(self, args): - """Print host metadata.""" - data = [('Client', 'Profile', "Groups", "Bundles")] + """ showclient [ ...] - Show metadata for the + given hosts """ if not len(args): - print("Usage:\nshowclient ... ") + print(self._get_usage(self.do_showclient)) return for client in args.split(): try: client_meta = self.build_metadata(client) - except: + except Bcfg2.Server.Plugin.MetadataConsistencyError: print("Client %s not defined" % client) continue fmt = "%-10s %s" @@ -590,7 +581,8 @@ class infoCore(cmd.Cmd, Bcfg2.Server.Core.BaseCore): print("=" * 80) def do_mappings(self, args): - """Print out mapping info.""" + """ mappings - Print generator mappings for + optional type and name """ # Dump all mappings unless type specified data = [('Plugin', 'Type', 'Name')] arglen = len(args.split()) @@ -610,38 +602,19 @@ class infoCore(cmd.Cmd, Bcfg2.Server.Core.BaseCore): for name in [name for name in names if name in generator.Entries.get(etype, {})]: data.append((generator.name, etype, name)) - printTabular(data) + print_tabular(data) - def do_event_debug(self, args): + def do_event_debug(self, _): + """ event_debug - Display filesystem events as they are + processed """ self.fam.debug = True - def do_cfgdebug(self, args): - try: - meta = self.build_metadata(args) - except Bcfg2.Server.Plugin.MetadataConsistencyError: - print("Unable to find metadata for host %s" % args) - 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_packageresolve(self, args): + """ packageresolve [...] - + Resolve the specified set of packages """ arglist = args.split(" ") if len(arglist) < 2: - print("Usage: packageresolve [...]") + print(self._get_usage(self.do_packageresolve)) return if 'Packages' not in self.plugins: @@ -651,7 +624,7 @@ class infoCore(cmd.Cmd, Bcfg2.Server.Core.BaseCore): initial = arglist[1:] metadata = self.build_metadata(hostname) self.plugins['Packages'].toggle_debug() - collection = self.plugins['Packages']._get_collection(metadata) + collection = self.plugins['Packages'].get_collection(metadata) packages, unknown = collection.complete(initial) newpkgs = list(packages.difference(initial)) print("%d initial packages" % len(initial)) @@ -664,8 +637,9 @@ class infoCore(cmd.Cmd, Bcfg2.Server.Core.BaseCore): print(" %s" % "\n ".join(unknown)) def do_packagesources(self, args): + """ packagesources - Show package sources """ if not args: - print("Usage: packagesources ") + print(self._get_usage(self.do_packagesources)) return if 'Packages' not in self.plugins: print("Packages plugin not enabled") @@ -675,31 +649,57 @@ class infoCore(cmd.Cmd, Bcfg2.Server.Core.BaseCore): except Bcfg2.Server.Plugin.MetadataConsistencyError: print("Unable to build metadata for host %s" % args) return - collection = self.plugins['Packages']._get_collection(metadata) + collection = self.plugins['Packages'].get_collection(metadata) print(collection.sourcelist()) def do_profile(self, arg): - """.""" - if not have_profile: + """ profile - Profile a single bcfg2-info + command """ + if not HAS_PROFILE: print("Profiling functionality not available.") return if len(arg) == 0: - print("Usage: profile ") + print(self._get_usage(self.do_profile)) return - tracefname = tempfile.mktemp() - p = profile.Profile() - p.runcall(self.onecmd, arg) - displayTrace(p) + prof = profile.Profile() + prof.runcall(self.onecmd, arg) + display_trace(prof) - def Run(self, args): - """.""" + def run(self, args): # pylint: disable=W0221 if args: self.onecmd(" ".join(args)) - os._exit(0) else: self.do_loop() -if __name__ == '__main__': + def _daemonize(self): + pass + + def _run(self): + pass + + def _block(self): + pass + + + +def build_usage(): + """ build usage message """ + cmd_blacklist = ["do_loop", "do_EOF"] + usage = dict() + for attrname in dir(InfoCore): + attr = getattr(InfoCore, attrname) + if (hasattr(attr, "__func__") and + attr.__func__.func_name not in cmd_blacklist and + attr.__func__.func_name.startswith("do_") and + attr.__func__.func_doc): + usage[attr.__name__] = re.sub(r'\s+', ' ', attr.__doc__) + return "Commands:\n" + "\n".join(usage[k] for k in sorted(usage.keys())) + + +USAGE = build_usage() + + +def main(): optinfo = dict(profile=Bcfg2.Options.CORE_PROFILE, interactive=Bcfg2.Options.INTERACTIVE, interpreter=Bcfg2.Options.INTERPRETER) @@ -712,24 +712,20 @@ if __name__ == '__main__': USAGE]) setup.parse(sys.argv[1:]) - if setup['debug']: - level = logging.DEBUG - elif setup['verbose']: - level = logging.INFO - else: - level = logging.WARNING - Bcfg2.Logger.setup_logging('bcfg2-info', to_syslog=False, - level=level) if setup['args'] and setup['args'][0] == 'help': print(setup.hm) sys.exit(0) - elif setup['profile'] and have_profile: + elif setup['profile'] and HAS_PROFILE: prof = profile.Profile() - loop = prof.runcall(infoCore, setup) - displayTrace(prof) + loop = prof.runcall(InfoCore, setup) + display_trace(prof) else: if setup['profile']: print("Profiling functionality not available.") - loop = infoCore(setup) + loop = InfoCore(setup) + + loop.run(setup['args']) - loop.Run(setup['args']) + +if __name__ == '__main__': + sys.exit(main()) diff --git a/testsuite/Testsrc/test_code_checks.py b/testsuite/Testsrc/test_code_checks.py index 3eaad90ae..eb7892ef3 100644 --- a/testsuite/Testsrc/test_code_checks.py +++ b/testsuite/Testsrc/test_code_checks.py @@ -53,8 +53,7 @@ contingent_checks = { # perform only error checking on the listed files error_checks = { - "sbin": ["bcfg2-build-reports", "bcfg2-info", "bcfg2-admin", - "bcfg2-reports"], + "sbin": ["bcfg2-build-reports", "bcfg2-admin", "bcfg2-reports"], "lib/Bcfg2": ["Proxy.py", "SSLServer.py"], "lib/Bcfg2/Server": ["Admin", "Reports", "SchemaUpdater"], "lib/Bcfg2/Client/Tools": ["launchd.py", @@ -69,9 +68,7 @@ error_checks = { "lib/Bcfg2/Server/Plugins": ["Decisions.py", "Deps.py", "Ldap.py", - "Pkgmgr.py", - "SSHbase.py", - "SSLCA.py"] + "Pkgmgr.py"] } # perform no checks at all on the listed files diff --git a/testsuite/pylintrc.conf b/testsuite/pylintrc.conf index e44ad36e7..de5db87f3 100644 --- a/testsuite/pylintrc.conf +++ b/testsuite/pylintrc.conf @@ -201,7 +201,7 @@ variable-rgx=[a-z_][a-z0-9_]{2,30}(ID)?$ inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ # Good variable names which should always be accepted, separated by a comma -good-names=_,rv,el,fd,ca,re,i,j,iv +good-names=_,rv,el,fd,ca,re,i,j,iv,ip # Bad variable names which should always be refused, separated by a comma bad-names=foo,bar,baz,toto,tutu,tata -- cgit v1.2.3-1-g7c22