#!/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()