def __init__(self, propagate=1, override=None, excepts=False): """ Constructor. :param propagate: If this evaluates to false, logging messages are not passed by this logger or by child loggers to higher level (ancestor) loggers. :type propagate: boolean :param override: Overrides the default log level for the intern logger. :type override: NOTSET, DEBUG, INFO, WARNING, ERROR, CRITICAL """ Validator.__init__(self) self.check_regex = re.compile(constants.CHECK_REGEX) self.handler_regex = re.compile(constants.HANDLER_REGEX) self.default_profile = False self.main = None self.errors = 0 self.excepts = excepts self.valid_checks = [] self.valid_handlers = [] self.logger = LoggerFactory().create(self.__module__, propagate, override) self.check_manager = CheckManager() self.handler_manager = HandlerManager()
def setup_method(self, _): # Make sure that it has not been yet instantiated LoggerFactory._instance = None logging.root = logging.RootLogger(logging.WARNING) logging.Logger.root = logging.root self.factory = LoggerFactory(config=_LOGGER_CONFIG.splitlines()) assert self.factory.create().root.handlers assert id(self.factory) == id(LoggerFactory())
def __init__(self, hook, repository_path): """ Constructor. :param hook: The hook that has to be executed. Valid values are constants.PRECOMMIT or constants.POSTCOMMIT. :type hook: constants.PRECOMMIT, constants.POSTCOMMIT. :param repository_path: The path to the current repository. :type repository_path: string """ self.hook = hook self.repository_path = repository_path self.checks = CheckManager() self.handlers = HandlerManager() self.result = constants.SUCCESS self.main = None self.transaction = None self.logger = LoggerFactory().create(self.__module__)
def validate(self, parser): """ Validates repoguard configuration files. :param parser: Command line parser. :type parser: optparse object. """ parser.add_option( "-q", "--quiet", action="store_false", dest="verbose", help="be vewwy quiet (I'm hunting wabbits).", default=True ) options, args = parser.parse_args() if len(args) != 2: parser.print_help() return 1 path = args[1] logging.basicConfig(format="%(message)s") logger = LoggerFactory().create('%s.tools.config' % constants.NAME) if options.verbose: level = logger.level else: level = logging.CRITICAL main_config = RepoGuardConfig(constants.CONFIG_PATH) config_validator = ConfigValidator(override=level) config_obj = ProjectConfig(path, template_dirs=main_config.template_dirs) for extend, path in config_obj.extended.iteritems(): logger.info("Extending %s (%s)", extend, path) else: logger.info("Nothing to extend.") if config_validator.validate(config_obj): return 0 else: return 1
class TestLoggerFactory(object): def setup_method(self, _): # Make sure that it has not been yet instantiated LoggerFactory._instance = None logging.root = logging.RootLogger(logging.WARNING) logging.Logger.root = logging.root self.factory = LoggerFactory(config=_LOGGER_CONFIG.splitlines()) assert self.factory.create().root.handlers assert id(self.factory) == id(LoggerFactory()) def test_create_default(self): logger = self.factory.create() assert logger.root.handlers[0].level == logging.ERROR assert len(logger.handlers) == 0 assert len(logger.root.handlers) >= 1 def test_create_undefined(self): logger = self.factory.create("DEFAULT") assert logger.root.handlers[0].level == logging.ERROR if len(logger.root.handlers) == 2: assert logger.root.handlers[1].level == logging.NOTSET def test_create_defined(self): logger = self.factory.create("repoguard.core.validator") assert logger.root.handlers[0].level == logging.INFO logger = self.factory.create("repoguard.foo.bar") assert logger.root.handlers[0].level == 42 def test_create_override(self): logger = self.factory.create("repoguard.core.validator", override=logging.INFO) assert logger.root.handlers[0].level == logging.INFO def test_create_unknown_error_level(self): pytest.raises(ValueError, self.factory.create, "repoguard.core.checker") def test_create_from_variable_def(self): logger = self.factory.create("repoguard.interpolation") assert logger.root.handlers[0].level == 42
def __new__(cls, transaction): """ Overriden __new__ method that handles the automatic transaction iniialisation. :param cls: The class that has to be created. :type cls: Module :param transaction: The current transaction for this module. :type transaction: C{Transaction} :return: Returns the created module object. :rtype: Module """ obj = super(Module, cls).__new__(cls) # Initialize transaction. obj.transaction = transaction # Generate an own logger for every module. obj.logger = LoggerFactory().create(cls.__module__) return obj
def validate(self, parser): """ Validates repoguard configuration files. :param parser: Command line parser. :type parser: optparse object. """ parser.add_option("-q", "--quiet", action="store_false", dest="verbose", help="be vewwy quiet (I'm hunting wabbits).", default=True) options, args = parser.parse_args() if len(args) != 2: parser.print_help() return 1 path = args[1] logging.basicConfig(format="%(message)s") logger = LoggerFactory().create('%s.tools.config' % constants.NAME) if options.verbose: level = logger.level else: level = logging.CRITICAL main_config = RepoGuardConfig(constants.CONFIG_PATH) config_validator = ConfigValidator(override=level) config_obj = ProjectConfig(path, template_dirs=main_config.template_dirs) for extend, path in config_obj.extended.iteritems(): logger.info("Extending %s (%s)", extend, path) else: logger.info("Nothing to extend.") if config_validator.validate(config_obj): return 0 else: return 1
def checker(hook, repo_path, txn_name, profile_name, halt_on_exception): """ Function to singularize the repoguard in precommit or postcommit mode. :param hook: Execute the repoguard as pre- or postcommit. :type hook: string :param repo_path: The path to the repository. :type repo_path: string :param txn_name: The name of the current transaction. :type txn_name: string :param halt_on_exception: Flag which indicates whether we halt on unexpected exceptions or not. :type halt_on_exception: boolean """ logger = LoggerFactory().create("%s.tools.checker" % constants.NAME) try: hooks_path = os.path.abspath(os.path.join(repo_path, "hooks")) project_config = os.path.join(hooks_path, constants.CONFIG_FILENAME) os.chdir(hooks_path) logger.debug("RepoGuard initializing...") repoguard = RepoGuard(hook, repo_path) logger.debug("Loading transaction...") repoguard.load_transaction(txn_name) logger.debug("Loading configuration...") main_config = RepoGuardConfig(constants.CONFIG_PATH) repoguard.load_config(main_config.template_dirs, project_config) logger.debug("Validating configuration...") if main_config.validate: repoguard.validate() else: logger.warning("Validation skipped.") logger.debug("RepoGuard running...") if profile_name: result = repoguard.run_profile(profile_name) else: result = repoguard.run() logger.debug("RepoGuard finished with %s.", result) if result == constants.SUCCESS: return 0 else: return 1 except validate.ValidateError: logger.exception("The configuration is invalid!") return 1 except: # pylint: disable=W0702 logger.exception( "An unexpected error occurred during the RepoGuard run! Halt on exception is '%s'." % halt_on_exception ) if not halt_on_exception: return 0 else: return 1
def checker(hook, repo_path, txn_name, profile_name, halt_on_exception): """ Function to singularize the repoguard in precommit or postcommit mode. :param hook: Execute the repoguard as pre- or postcommit. :type hook: string :param repo_path: The path to the repository. :type repo_path: string :param txn_name: The name of the current transaction. :type txn_name: string :param halt_on_exception: Flag which indicates whether we halt on unexpected exceptions or not. :type halt_on_exception: boolean """ logger = LoggerFactory().create('%s.tools.checker' % constants.NAME) try: hooks_path = os.path.abspath(os.path.join(repo_path, "hooks")) project_config = os.path.join(hooks_path, constants.CONFIG_FILENAME) os.chdir(hooks_path) logger.debug("RepoGuard initializing...") repoguard = RepoGuard(hook, repo_path) logger.debug("Loading transaction...") repoguard.load_transaction(txn_name) logger.debug("Loading configuration...") main_config = RepoGuardConfig(constants.CONFIG_PATH) repoguard.load_config(main_config.template_dirs, project_config) logger.debug("Validating configuration...") if main_config.validate: repoguard.validate() else: logger.warning("Validation skipped.") logger.debug("RepoGuard running...") if profile_name: result = repoguard.run_profile(profile_name) else: result = repoguard.run() logger.debug("RepoGuard finished with %s.", result) if result == constants.SUCCESS: return 0 else: return 1 except validate.ValidateError: logger.exception("The configuration is invalid!") return 1 except: # pylint: disable=W0702 logger.exception( "An unexpected error occurred during the RepoGuard run! Halt on exception is '%s'." % halt_on_exception) if not halt_on_exception: return 0 else: return 1
class RepoGuard(object): """ Main RepoGuard class. """ def __init__(self, hook, repository_path): """ Constructor. :param hook: The hook that has to be executed. Valid values are constants.PRECOMMIT or constants.POSTCOMMIT. :type hook: constants.PRECOMMIT, constants.POSTCOMMIT. :param repository_path: The path to the current repository. :type repository_path: string """ self.hook = hook self.repository_path = repository_path self.checks = CheckManager() self.handlers = HandlerManager() self.result = constants.SUCCESS self.main = None self.transaction = None self.logger = LoggerFactory().create(self.__module__) def load_transaction(self, name): """ Load the transaction with the given name. :param name: The name of the current transaction. :type name: string """ self.transaction = Transaction(self.repository_path, name) def load_config(self, tpl_dirs, config): """ Load the project configuration. :param tpl_dirs: Path lists where all templates are located. :type tpl_dirs: string :param config: The path or a splittedline project configuration string. :type config: string """ self.logger.debug("Loading project configuration...") hooks_path = os.path.join(self.repository_path, "hooks") self.main = ProjectConfig(config, hooks_path, tpl_dirs) self.logger.debug("Project configuration loaded.") def validate(self): """ Runs the internal validation process of the current loaded configuration. :return: Returns the status code of the validator. succes = 0, error > 0 :rtype: integer """ validator = ConfigValidator(excepts=True) return validator.validate(self.main) def _combined_profile_regexes(self): """ Returns a regular expression string which matches all files that are covered by any special profile. A special profile defines a non-empty regex parameter. """ combined_profile_regexes = "" for profile in self.main.profiles: if not profile.regex is None: combined_profile_regexes += "(%s)|" % profile.regex if not combined_profile_regexes: combined_profile_regexes = None else: combined_profile_regexes = combined_profile_regexes[:-1] return combined_profile_regexes def run(self): """ Execution of the checking _process and handler handling. It is recommended to call the load_config method before calling this method. :return: Returns the _process result as a constant string. :rtype: constants.SUCCESS, constants.ERROR """ try: self.logger.debug("Running run...") combined_profile_regexes = self._combined_profile_regexes() self.logger.debug("Default ignore regex: %s", combined_profile_regexes) # Process executing for profile in self.main.profiles: ignores = list() if not profile.regex is None: self.transaction.profile = profile.regex else: # default profile: covers all files # which are not handled by a special profile self.transaction.profile = ".*" if not combined_profile_regexes is None: ignores = [combined_profile_regexes] # if there are no files in this profile continue. if not self.transaction.get_files(ignore_list=ignores): self.logger.debug("Profile '%s' skipped.", profile.name) continue self._run_profile(profile) self.logger.debug("Run finished with %s.", self.result) return self.result finally: self.logger.debug("Cleaning up transaction.") self.transaction.cleanup() def run_profile(self, name): """ Runs a specific profile. """ try: profile_found = False for profile in self.main.profiles: if name == profile.name: self._run_profile(profile) profile_found = True if not profile_found: self.result = constants.ERROR self.logger.error("No profile with name '%s' exists." % name) else: self.logger.debug("Run finished with %s.", self.result) return self.result finally: self.logger.debug("Cleaning up transaction.") self.transaction.cleanup() def _run_profile(self, profile): process = profile.get_process(self.hook) if not process: self.logger.debug( "%s process skipped." % self.hook.capitalize() ) return self.logger.debug("Running profile '%s'...", profile.name) protocol = Protocol(profile.name) # run the configured checks for name, config, interp in process.checks: self.logger.debug("Loading check %s...", name) check = self.checks.fetch(name, self.transaction) self.logger.debug("Starting check %s...", name) entry = check.run(config, interp) self.logger.debug( "Check %s finished with %s.", name, entry.result ) protocol.append(entry) # run the configured handlers when a message was returned if entry.msg: self.logger.debug( "Running handler after check %s...", entry.check ) self.handlers.singularize(self.transaction, process, entry) self.logger.debug( "Handler after check %s finished.", entry.check ) # cancel the _process chain when an abortonerror was detected. if interp == constants.ABORTONERROR and not protocol.success: msg = "Profile %s aborted after check %s." self.logger.debug(msg, profile.name, entry.check) break # cumulativ execution of all handlers. self.logger.debug("Running handler summarize...") self.handlers.summarize(self.transaction, process, protocol) self.logger.debug("Handler summarize finished.") if not protocol.success: self.result = constants.ERROR self.logger.debug("Profile %s finished.", profile.name)
class ConfigValidator(Validator): """ The _config validator class validates a given ConfigObj object. """ def __init__(self, propagate=1, override=None, excepts=False): """ Constructor. :param propagate: If this evaluates to false, logging messages are not passed by this logger or by child loggers to higher level (ancestor) loggers. :type propagate: boolean :param override: Overrides the default log level for the intern logger. :type override: NOTSET, DEBUG, INFO, WARNING, ERROR, CRITICAL """ Validator.__init__(self) self.check_regex = re.compile(constants.CHECK_REGEX) self.handler_regex = re.compile(constants.HANDLER_REGEX) self.default_profile = False self.main = None self.errors = 0 self.excepts = excepts self.valid_checks = [] self.valid_handlers = [] self.logger = LoggerFactory().create(self.__module__, propagate, override) self.check_manager = CheckManager() self.handler_manager = HandlerManager() def validate(self, main): """ Validates the given ConfigObj object. :param main: The configuration that has to be validated. :type main: ConfigObj :raise ValidateError: When a the given ConfigObj object is not in a valid form a ValidateError is raised. **Usage** >>> main = ConfigObj() >>> validator = ConfigValidator() >>> try: >>> validator.validate(main) >>> except ValidateError, exc: >>> print exc """ self.default_profile = False self.errors = 0 self.main = main self.logger.info("Starting validation...") try: if 'extends' in self.main: self.check('string', self.main['extends']) self.logger.info("Validating profiles...") if 'profiles' in self.main: for profile, config in self.main['profiles'].iteritems(): self._validate_profile(profile, config) if not self.default_profile: self.exception("No default profile found.") self.logger.info("Validating check configurations...") if 'checks' in self.main: self._validate_checks(self.main['checks']) else: self.logger.info("No check configurations defined.") self.logger.info("Validating handler configurations...") if 'handlers' in self.main: self._validate_handlers(self.main['handlers']) else: self.logger.info("No handler configurations defined.") finally: self.logger.info("Validation finished with %s.", self.errors) return self.errors def _validate_checks(self, config): """ Validates all checks. """ for check, conf in config.iteritems(): if not check in self.check_manager.available_modules: msg = "The check '%s' is not available. Check the spelling." self.exception(msg % check) continue for check_config in conf.values(): self._validate_check_config(check, check_config) def _validate_handlers(self, config): """ Validates all handlers. """ for handler, conf in config.iteritems(): if not handler in self.handler_manager.available_modules: msg = "The handler '%s' is not available. Check the spelling." self.exception(msg % handler) continue for handler_config in conf.values(): self._validate_handler_config(handler, handler_config) def _validate_profile(self, profile, config): """ Validates a given profile. @param profile: The profile that has to be validated. @type profile: list<string> @param _config: The main _config. @type _config: ConfigObj """ if 'regex' not in config: if self.default_profile: msg = "Default profile already exists. " \ + "Only one default profile is allowed." self.exception(msg) else: self.default_profile = True self.logger.info("Default profile found.") else: self.check('string', config['regex']) for commit in constants.HOOKS: if commit in config: self._validate_profile_commit(profile, commit, config[commit]) def _validate_profile_commit(self, profile, commit, config): """ Validates a given precommit or postcommit section. @param profile: The profile that has to be checked. @type profile: string @param commit: The pre or postcommit section of the profile. @type commit: constants.PRECOMMIT, constants.POSTCOMMIT @param _config: The main ConfigObj object. @type _config: ConfigObj """ for key in ('checks', 'success', 'error'): if key not in config: msg = "No %s found for profile '%s' in %s process" self.exception(msg % (key, profile, commit)) if 'default' in config and not config['default'] in constants.INTERPS: msg = "Unknown default value '%s' in profile '%s'" self.exception(msg % (config['default'], profile)) self._validate_process_check(config['checks']) self._validate_process_handler(config['success']) self._validate_process_handler(config['error']) def _validate_process_check(self, process): """ Validates the checks of the given process. @param process: The process that contains the checks. @type process: dict """ process = self.check('string_list', process) for check in process: result = self.check_regex.search(check) name, config, interp = result.group("name", "config", "interp") if not name: self.logger.error("Empty checks are not allowed.") if config: try: self.main['checks'][name][config] except KeyError: msg = "Configuration '%s' for check '%s' is not defined" self.exception(msg % (config, name)) else: self._validate_emtpy_check_config(name) if interp and not interp in constants.INTERPS: msg = "Error interpretation '%s' is not valid." self.exception(msg % interp) def _validate_process_handler(self, process): """ Validates the handlers of the given process. @param process: The process that contains the handlers. @type process: dict """ process = self.check('string_list', process) for handler in process: result = self.handler_regex.search(handler) name, config = result.group("name", "config") if not name: self.logger.error("Empty handlers are not allowed.") if config: try: self.main['handlers'][name][config] except KeyError: msg = "Configuration '%s' for check '%s' is not defined" self.exception(msg % (config, name)) else: self._validate_empty_handler_config(name) def _validate_check_config(self, check, config): """ Validates the _config that is specified by the given check. @param check: The check of which the _config has to be validated. @type check: string @param config: The _config of the check. @type config: Section """ try: check_class = self.check_manager.load(check) check_class.__config__.from_config(config) except ImportError, exc: msg = "Unable to load check '%s'." self.exception(msg % check) except (ValueError, KeyError), exc: msg = "Validation error in check '%s': '%s'" self.exception(msg % (check, str(exc)))
class RepoGuard(object): """ Main RepoGuard class. """ def __init__(self, hook, repository_path): """ Constructor. :param hook: The hook that has to be executed. Valid values are constants.PRECOMMIT or constants.POSTCOMMIT. :type hook: constants.PRECOMMIT, constants.POSTCOMMIT. :param repository_path: The path to the current repository. :type repository_path: string """ self.hook = hook self.repository_path = repository_path self.checks = CheckManager() self.handlers = HandlerManager() self.result = constants.SUCCESS self.main = None self.transaction = None self.logger = LoggerFactory().create(self.__module__) def load_transaction(self, name): """ Load the transaction with the given name. :param name: The name of the current transaction. :type name: string """ self.transaction = Transaction(self.repository_path, name) def load_config(self, tpl_dirs, config): """ Load the project configuration. :param tpl_dirs: Path lists where all templates are located. :type tpl_dirs: string :param config: The path or a splittedline project configuration string. :type config: string """ self.logger.debug("Loading project configuration...") hooks_path = os.path.join(self.repository_path, "hooks") self.main = ProjectConfig(config, hooks_path, tpl_dirs) self.logger.debug("Project configuration loaded.") def validate(self): """ Runs the internal validation process of the current loaded configuration. :return: Returns the status code of the validator. succes = 0, error > 0 :rtype: integer """ validator = ConfigValidator(excepts=True) return validator.validate(self.main) def _combined_profile_regexes(self): """ Returns a regular expression string which matches all files that are covered by any special profile. A special profile defines a non-empty regex parameter. """ combined_profile_regexes = "" for profile in self.main.profiles: if not profile.regex is None: combined_profile_regexes += "(%s)|" % profile.regex if not combined_profile_regexes: combined_profile_regexes = None else: combined_profile_regexes = combined_profile_regexes[:-1] return combined_profile_regexes def run(self): """ Execution of the checking _process and handler handling. It is recommended to call the load_config method before calling this method. :return: Returns the _process result as a constant string. :rtype: constants.SUCCESS, constants.ERROR """ try: self.logger.debug("Running run...") combined_profile_regexes = self._combined_profile_regexes() self.logger.debug("Default ignore regex: %s", combined_profile_regexes) # Process executing for profile in self.main.profiles: ignores = list() if not profile.regex is None: self.transaction.profile = profile.regex else: # default profile: covers all files # which are not handled by a special profile self.transaction.profile = ".*" if not combined_profile_regexes is None: ignores = [combined_profile_regexes] # if there are no files in this profile continue. if not self.transaction.get_files(ignore_list=ignores): self.logger.debug("Profile '%s' skipped.", profile.name) continue self._run_profile(profile) self.logger.debug("Run finished with %s.", self.result) return self.result finally: self.logger.debug("Cleaning up transaction.") self.transaction.cleanup() def run_profile(self, name): """ Runs a specific profile. """ try: profile_found = False for profile in self.main.profiles: if name == profile.name: self._run_profile(profile) profile_found = True if not profile_found: self.result = constants.ERROR self.logger.error("No profile with name '%s' exists." % name) else: self.logger.debug("Run finished with %s.", self.result) return self.result finally: self.logger.debug("Cleaning up transaction.") self.transaction.cleanup() def _run_profile(self, profile): process = profile.get_process(self.hook) if not process: self.logger.debug("%s process skipped." % self.hook.capitalize()) return self.logger.debug("Running profile '%s'...", profile.name) protocol = Protocol(profile.name) # run the configured checks for name, config, interp in process.checks: self.logger.debug("Loading check %s...", name) check = self.checks.fetch(name, self.transaction) self.logger.debug("Starting check %s...", name) entry = check.run(config, interp) self.logger.debug("Check %s finished with %s.", name, entry.result) protocol.append(entry) # run the configured handlers when a message was returned if entry.msg: self.logger.debug("Running handler after check %s...", entry.check) self.handlers.singularize(self.transaction, process, entry) self.logger.debug("Handler after check %s finished.", entry.check) # cancel the _process chain when an abortonerror was detected. if interp == constants.ABORTONERROR and not protocol.success: msg = "Profile %s aborted after check %s." self.logger.debug(msg, profile.name, entry.check) break # cumulativ execution of all handlers. self.logger.debug("Running handler summarize...") self.handlers.summarize(self.transaction, process, protocol) self.logger.debug("Handler summarize finished.") if not protocol.success: self.result = constants.ERROR self.logger.debug("Profile %s finished.", profile.name)