#!/usr/bin/python3
# -*- coding: utf-8 -*-
#
# Univention UCS@school
# Copyright 2018-2021 Univention GmbH
#
# https://www.univention.de/
#
# All rights reserved.
#
# The source code of this program is made available
# under the terms of the GNU Affero General Public License version 3
# (GNU AGPL V3) as published by the Free Software Foundation.
#
# Binary versions of this program provided by Univention to you as
# well as other copyrighted, protected or trademarked materials like
# Logos, graphics, fonts, specific documentations and configurations,
# cryptographic keys etc. are subject to a license agreement between
# you and Univention and not subject to the GNU AGPL V3.
#
# In the case you use this program under the terms of the GNU AGPL V3,
# the program is provided in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public
# License with the Debian GNU/Linux or Univention distribution in file
# /usr/share/common-licenses/AGPL-3; if not, see
# <http://www.gnu.org/licenses/>.
"""
Configuration checks.
After the configuration has been read, checks run.
To add your own checks, subclass :py:class:`ConfigurationChecks`, save the
module in ``/usr/share/ucs-school-import/checks`` and add its module name
(without ``.py``) to the list in the configuration key ``configuration_checks``.
Remove ``defaults`` from your ``configuration_checks`` only if you know what you
are doing.
----
Example: Save the following to ``/usr/share/ucs-school-import/checks/mychecks.py``:
>>> from ucsschool.importer.exceptions import InitialisationError
>>> from ucsschool.importer.utils.configuration_checks import ConfigurationChecks
>>>
>>> class MyConfigurationChecks(ConfigurationChecks):
>>> 	def test_nonzero_deactivation_grace(self):
>>> 		if self.config.get('deletion_grace_period', {}).get('deactivation', 0) == 0:
>>> 			raise InitialisationError('deletion_grace_period:deactivation must not be zero.')
Then add a configuration entry to ``/var/lib/ucs-school-import/configs/user_import.json``::
    {
    [..]
        "configuration_checks": ["defaults", "mychecks"]
    }
"""
from __future__ import absolute_import
import inspect
import logging
from operator import itemgetter
from typing import TYPE_CHECKING, List, Type
from ucsschool.lib.pyhooks.pyhooks_loader import PyHooksLoader
from ..exceptions import UcsSchoolImportFatalError
from .ldap_connection import get_readonly_connection, get_unprivileged_connection
if TYPE_CHECKING:
    from ..configuration import ReadOnlyDict
__all__ = ["ConfigurationChecks"]
CONFIG_CHECKS_CODE_DIR = "/usr/share/ucs-school-import/checks"
[docs]class ConfigurationChecks(object):
    """
    Base class for configuration checks.
    Provides the configuration singleton in :py:attr:`self.config`, a
    read-only LDAP connection object in :py:attr:`self.lo` and a logging
    instance in :py:attr:`self.logger`.
    All methods with names starting with ``test_`` will be executed in
    alphanumerical order. Failing tests should raise a
    py:exception:`ucsschool.importer.exceptions.InitialisationError` exception.
    """
    def __init__(self, config):  # type: (ReadOnlyDict) -> None
        self.config = config
        try:
            self.lo, po = get_readonly_connection()
        except UcsSchoolImportFatalError:
            self.lo, po = get_unprivileged_connection()
        self.logger = logging.getLogger(__name__) 
def run_configuration_checks(config):  # type: (ReadOnlyDict) -> None
    def is_module_in_config(kls):  # type: (Type[object]) -> bool
        return kls.__module__ in config.get("configuration_checks", [])
    logger = logging.getLogger(__name__)
    loader = PyHooksLoader(CONFIG_CHECKS_CODE_DIR, ConfigurationChecks, logger, is_module_in_config)
    config_check_classes = loader.get_hook_classes()  # type: List[Type[ConfigurationChecks]]
    disabled_checks = config.get("disabled_checks", [])
    for kls in config_check_classes:
        cc = kls(config)
        test_methods = inspect.getmembers(
            cc, lambda x: inspect.ismethod(x) and x.__name__.startswith("test_")
        )
        test_methods.sort(key=itemgetter(0))
        for name, method in test_methods:
            if name in disabled_checks:
                logger.warning("Skipping configuration check %r.", name)
                continue
            method()