#!/usr/bin/env python """Query reporting system for client status.""" import os import sys import datetime from optparse import OptionParser, OptionGroup, make_option from Bcfg2.Compat import ConfigParser try: import Bcfg2.settings except ConfigParser.NoSectionError: print("Your bcfg2.conf is currently missing the [database] section which " "is necessary for the reporting interface. Please see bcfg2.conf(5) " "for more details.") sys.exit(1) project_directory = os.path.dirname(Bcfg2.settings.__file__) project_name = os.path.basename(project_directory) sys.path.append(os.path.join(project_directory, '..')) project_module = __import__(project_name, '', '', ['']) sys.path.pop() # Set DJANGO_SETTINGS_MODULE appropriately. os.environ['DJANGO_SETTINGS_MODULE'] = '%s.settings' % project_name from Bcfg2.Reporting.models import (Client, BaseEntry) from django import db 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_type == entry[0] and item.name == entry[1]): result.append(client) return result def print_fields(fields, client, fmt, extra=None): """ Prints the fields specified in fields of client, max_name specifies the column width of the name column. """ fdata = [] if extra is None: extra = dict() for field in fields: if field == 'time': fdata.append(str(client.current_interaction.timestamp)) elif field == 'state': if client.current_interaction.isclean(): fdata.append("clean") else: fdata.append("dirty") elif field == 'total': fdata.append(client.current_interaction.total_count) elif field == 'good': fdata.append(client.current_interaction.good_count) elif field == 'modified': fdata.append(client.current_interaction.modified_count) elif field == 'extra': fdata.append(client.current_interaction.extra_count) elif field == 'bad': fdata.append(client.current_interaction.bad_count) else: try: fdata.append(getattr(client, field)) except: fdata.append(extra.get(field, "N/A")) print(fmt % tuple(fdata)) def print_entries(interaction, etype): items = getattr(interaction, etype)() for item in items: print("%-70s %s" % (item.entry_type + ":" + item.name, etype)) def main(): parser = OptionParser(usage="%prog [options] [arg]") # 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) # 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) options, args = parser.parse_args() # 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: client.expiration = None print("Host un-expired.") client.save() elif options.total: managed = client.current_interaction.total_count good = client.current_interaction.good_count 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") if options.modified or options.show: print_entries(client.current_interaction, "modified") if options.extra or options.show: print_entries(client.current_interaction, "extra") else: clients = Client.objects.exclude(current_interaction__isnull=True) result = list() edata = dict() fields = options.fields.split(',') if mode_family == allhostmodes: if args: print("%s do not take any arguments, ignoring" % allhostmodes.title) 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 = sys.exc_info()[1] 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_cls = BaseEntry.entry_from_type(entries[0][0]) except ValueError: print("Unhandled/unkown type %s" % entries[0][0]) return 2 # todo batch fetch this. sqlite could break for client in clients: ents = entry_cls.objects.filter(name=entries[0][1], interaction=client.current_interaction) if len(ents) == 0: continue edata[client] = {"entry state": ents[0].get_state_display(), "reason": ents[0]} result.append(client) if 'name' not in fields: fields.insert(0, "name") if not result: print("No match found") return 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)) db.close_connection() if __name__ == "__main__": sys.exit(main())