#!/usr/bin/env python

"""This tool examines your Bcfg2 specifications for errors."""
__revision__ = '$Revision$'

import sys
import inspect
import logging
import Bcfg2.Logger
import Bcfg2.Options
import Bcfg2.Server.Core
import Bcfg2.Server.Lint
# Compatibility imports
from Bcfg2.Bcfg2Py3k import ConfigParser

logger = logging.getLogger('bcfg2-lint')

def run_serverless_plugins(plugins, config=None, setup=None, errorhandler=None):
    logger.debug("Running serverless plugins")
    for plugin_name, plugin in list(plugins.items()):
        run_plugin(plugin, plugin_name, errorhandler=errorhandler,
                   setup=setup, config=config, files=files)

def run_server_plugins(plugins, config=None, setup=None, errorhandler=None):
    core = load_server(setup)
    logger.debug("Running server plugins")
    for plugin_name, plugin in list(plugins.items()):
        run_plugin(plugin, plugin_name, args=[core], errorhandler=errorhandler,
                   setup=setup, config=config, files=files)

def run_plugin(plugin, plugin_name, setup=None, errorhandler=None,
               args=None, config=None, files=None):
    logger.debug("  Running %s" % plugin_name)
    if args is None:
        args = []

    if errorhandler is None:
        errorhandler = get_errorhandler(config)

    if config is not None and config.has_section(plugin_name):
        arg = setup
        for key, val in config.items(plugin_name):
            arg[key] = val
        args.append(arg)
    else:
        args.append(setup)
        
    # older versions of python do not support mixing *-magic and
    # non-*-magic (e.g., "plugin(*args, files=files)", so we do this
    # all with *-magic
    kwargs = dict(files=files, errorhandler=errorhandler)
    
    return plugin(*args, **kwargs).Run()

def get_errorhandler(config):
    """ get a Bcfg2.Server.Lint.ErrorHandler object """
    if config.has_section("errors"):
        conf = dict(config.items("errors"))
    else:
        conf = None
    return Bcfg2.Server.Lint.ErrorHandler(config=conf)

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
    core.fam.handle_events_in_interval(4)
    return core

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),
        })
    setup = Bcfg2.Options.OptionParser(optinfo)
    setup.parse(sys.argv[1:])

    log_args = dict(to_syslog=False, to_console=logging.WARNING)
    if setup['verbose']:
        log_args['to_console'] = logging.DEBUG
    Bcfg2.Logger.setup_logging('bcfg2-info', **log_args)

    config = ConfigParser.SafeConfigParser()
    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']
    elif "bcfg2-repo-validate" in sys.argv[0]:
        allplugins = 'Duplicates,RequiredAttrs,Validate'.split(',')
    else:
        try:
            allplugins = config.get('lint', 'plugins').split(',')
        except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
            allplugins = 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:
        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)
        if [c for c in inspect.getmro(plugin)
            if c == Bcfg2.Server.Lint.ServerPlugin]:
            serverplugins[plugin_name] = plugin
        else:
            serverlessplugins[plugin_name] = plugin

    errorhandler = get_errorhandler(config)

    run_serverless_plugins(serverlessplugins,
                           errorhandler=errorhandler,
                           config=config, setup=setup)

    if serverplugins:
        if errorhandler.errors:
            # it would be swell if we could try to start the server
            # even if there were errors with the serverless plugins,
            # but since XML parsing errors occur in the FAM thread
            # (not in the core server thread), there's no way we can
            # start the server and try to catch exceptions --
            # bcfg2-lint isn't in the same stack as the exceptions.
            # so we're forced to assume that a serverless plugin error
            # will prevent the server from starting
            print("Serverless plugins encountered errors, skipping server "
                  "plugins")
        else:
            run_server_plugins(serverplugins, errorhandler=errorhandler,
                               config=config, setup=setup)

    if errorhandler.errors or errorhandler.warnings or setup['verbose']:
        print("%d errors" % errorhandler.errors)
        print("%d warnings" % errorhandler.warnings)

    if errorhandler.errors:
        raise SystemExit(2)
    elif errorhandler.warnings:
        raise SystemExit(3)
