Source code for univention.testing.udm_extensions

# SPDX-FileCopyrightText: 2013-2025 Univention GmbH
# SPDX-License-Identifier: AGPL-3.0-only


import subprocess
import sys
from collections.abc import Iterable, Mapping, Sequence

from univention.config_registry import ConfigRegistry
from univention.testing.strings import random_name, random_version
from univention.testing.utils import fail, get_ldap_connection


VALID_EXTENSION_TYPES = ('hook', 'syntax', 'module')


[docs] def get_package_name() -> str: """returns a valid package name""" return random_name()
[docs] def get_package_version() -> str: return random_version()
[docs] def get_extension_name(extension_type: str) -> str: """ Returns a valid extension name for the given extension type. >>> get_extension_name('hook') 'jcvuardfqx' >>> get_extension_name('syntax') 'hwvkm29tde' >>> get_extension_name('module') 'ucstest/r3jkljngcp' """ assert extension_type in VALID_EXTENSION_TYPES if extension_type == 'module': return 'ucstest/%s' % random_name() else: return random_name()
[docs] def get_extension_filename(extension_type: str, extension_name: str) -> str: assert extension_type in VALID_EXTENSION_TYPES return '%s.py' % extension_name
[docs] def call_cmd(cmd: Sequence[str], fail_on_error: bool = True) -> int: """Calls the given cmd (list of strings).""" print('CMD: %r' % cmd) sys.stdout.flush() exitcode = subprocess.call(cmd) if fail_on_error and exitcode: fail(f'EXITCODE of script {cmd[0]}: {exitcode!r}') return exitcode
[docs] def call_join_script(name: str, fail_on_error: bool = True) -> int: """ Calls the given join script (e.g. name='66foobar.inst'). If fail is true, then the function fail() is called if the exitcode is not zero. """ ucr = ConfigRegistry() ucr.load() return call_cmd(['/usr/lib/univention-install/%s' % name, '--binddn', ucr.get('tests/domainadmin/account'), '--bindpwdfile', ucr.get('tests/domainadmin/pwdfile')], fail_on_error=fail_on_error)
[docs] def call_unjoin_script(name: str, fail_on_error: bool = True) -> int: """ Calls the given unjoin script (e.g. name='66foobar-uninstall.uinst'). If fail is true, then the function fail() is called if the exitcode is not zero. """ ucr = ConfigRegistry() ucr.load() return call_cmd(['/usr/lib/univention-uninstall/%s' % name, '--binddn', ucr.get('tests/domainadmin/account'), '--bindpwdfile', ucr.get('tests/domainadmin/pwdfile')], fail_on_error=fail_on_error)
[docs] def get_syntax_buffer(name: str | None = None, identifier: str | None = None) -> str: """ Returns a UDM syntax with given name (e.g. 'MySimpleHook'). If name is omitted, a randomly generated name is used. """ if name is None: name = random_name() if identifier is None: identifier = name return '''# UCS-TEST SYNTAX %(syntax_identifier)s class %(syntax_name)s(simple): regex = re.compile('^ucstest-[0-9A-Za-z]+$') error_message = 'Wrong value given for ucs-test-syntax!' ''' % {'syntax_name': name, 'syntax_identifier': identifier}
[docs] def get_hook_buffer(name: str | None = None, identifier: str | None = None) -> str: """ Returns a UDM hook with given name (e.g. 'MySimpleHook'). If name is omitted, a randomly generated name is used. """ if name is None: name = random_name() if identifier is None: identifier = name return '''# UCS-TEST HOOK %(hook_identifier)s from univention.admin.hook import simpleHook class %(hook_name)s(simpleHook): type = 'SetDescriptionValue' def hook_ldap_pre_modify(self, obj): """ Set description consisting of username, lastname """ obj['description'] = 'USERNAME=%%(username)s LASTNAME=%%(lastname)s' %% obj.info ''' % {'hook_name': name, 'hook_identifier': identifier}
[docs] def get_module_buffer(name: str | None = None, identifier: str | None = None) -> str: """ Returns a UDM module with given name (e.g. 'testing/mytest'). If name is omitted, a randomly generated name is used ('ucstest/%(randomstring)s'). """ if name is None: name = f'ucstest/{random_name()}' assert '/' in name if identifier is None: identifier = name return '''# this UDM module has been created by ucs-test from univention.admin.layout import Tab, Group import univention.admin.filter import univention.admin.handlers import univention.admin.allocators import univention.admin.localization import univention.debug as ud translation = univention.admin.localization.translation('univention.admin.handlers.ucstest') _ = translation.translate module = '%(module_udmname)s' operations = ['add','edit','remove','search'] childs = 0 short_description = _('UCS-TEST MODULE %(module_identifier)s') long_description = '' options = { 'default': univention.admin.option( short_description=short_description, default=True, objectClasses=['top', 'automountMap'], ) } property_descriptions = { 'name': univention.admin.property( short_description=_('Name'), long_description='', syntax=univention.admin.syntax.string, include_in_default_search=True, required=True, may_change=False, identifies=True ), } layout = [ Tab( _( 'General' ), _( 'Basic settings' ), layout = [ Group( _( 'General' ), layout = [[ "name" ]] ), ] ) ] mapping = univention.admin.mapping.mapping() mapping.register('name', 'ou', None, univention.admin.mapping.ListToString) class object(univention.admin.handlers.simpleLdap): module = module lookup = object.lookup identify = object.identify ''' % {'module_udmname': name, 'module_identifier': identifier}
[docs] def get_extension_buffer(extension_type: str, name: str | None = None, identifier: str | None = None) -> str: """ Get UDM extension of specified type with specified name. In case the name is omitted, a random name will be used. """ assert extension_type in VALID_EXTENSION_TYPES return { 'hook': get_hook_buffer, 'syntax': get_syntax_buffer, 'module': get_module_buffer, }[extension_type](name, identifier)
[docs] def get_postinst_script_buffer(extension_type: str, filename: str, app_id: str | None = None, version_start: str | None = None, version_end: str | None = None, options: Mapping[str, str | Iterable[str]] | None = None) -> str: """ Returns a postinst script that registers the given file as UDM extension with extension type ('hook', 'syntax' or 'module'). Optionally UNIVENTION_APP_ID, UCS version start and UCS version end may be specified. """ assert extension_type in VALID_EXTENSION_TYPES app_id = '' if not app_id else 'export UNIVENTION_APP_IDENTIFIER="%s"' % app_id version_start = '' if not version_start else '--ucsversionstart %s' % version_start version_end = '' if not version_end else '--ucsversionend %s' % version_end other_options = '' if options: for key in options: if isinstance(options[key], str): other_options += ' --%s %s' % (key, options[key]) else: other_options += ' --%s ' % (key,) + ' --%s '.join(options[key]) return '''#!/bin/sh set -e #DEBHELPER# %(app_id)s . /usr/share/univention-lib/ldap.sh ucs_registerLDAPExtension "$@" --udm_%(extension_type)s %(filename)s %(version_start)s %(version_end)s %(other_options)s exit 0 ''' % { 'filename': filename, 'extension_type': extension_type, 'app_id': app_id, 'version_start': version_start, 'version_end': version_end, 'other_options': other_options, }
[docs] def get_postrm_script_buffer(extension_type: str, extension_name: str, package_name: str) -> str: """ Returns an postrm script that deregisters the given UDM extension. The type of the extension has to be specified ('hook', 'syntax' or 'module'). """ assert extension_type in VALID_EXTENSION_TYPES if extension_type == 'module': assert '/' in extension_name return '''#!/bin/sh set -e #DEBHELPER# . /usr/share/univention-lib/ldap.sh ucs_unregisterLDAPExtension "$@" --udm_%(extension_type)s %(extension_name)s exit 0 ''' % {'extension_name': extension_name, 'extension_type': extension_type}
[docs] def get_join_script_buffer(extension_type: str, filename: str, app_id: str | None = None, joinscript_version: int = 1, version_start: str | None = None, version_end: str | None = None, options: Mapping[str, str | Iterable[str]] | None = None) -> str: """ Returns a join script that registers the given file as UDM extension with extension type ('hook', 'syntax' or 'module'). Optionally a joinscript version, UNIVENTION_APP_ID, UCS version start and UCS version end may be specified. """ assert extension_type in VALID_EXTENSION_TYPES app_id = '' if not app_id else 'export UNIVENTION_APP_IDENTIFIER="%s"' % app_id version_start = '' if not version_start else '--ucsversionstart %s' % version_start version_end = '' if not version_end else '--ucsversionend %s' % version_end other_options = '' if options: for key in options: if isinstance(options[key], str): other_options += ' --%s %s' % (key, options[key]) else: other_options += ' --%s ' % (key,) + (' --%s ' % (key,)).join(options[key]) return '''#!/bin/sh VERSION=%(joinscript_version)s set -e . /usr/share/univention-join/joinscripthelper.lib joinscript_init %(app_id)s . /usr/share/univention-lib/ldap.sh ucs_registerLDAPExtension "$@" --udm_%(extension_type)s %(filename)s %(version_start)s %(version_end)s %(other_options)s joinscript_save_current_version exit 0 ''' % { 'filename': filename, 'joinscript_version': joinscript_version, 'extension_type': extension_type, 'app_id': app_id, 'version_start': version_start, 'version_end': version_end, 'other_options': other_options, }
[docs] def get_unjoin_script_buffer(extension_type: str, extension_name: str, package_name: str) -> str: """ Returns an unjoin script that deregisters the given UDM extension. The type of the extension has to be specified ('hook', 'syntax' or 'module'). """ assert extension_type in VALID_EXTENSION_TYPES if extension_type == 'module': assert '/' in extension_name return '''#!/bin/sh VERSION=1 set -e . /usr/share/univention-join/joinscripthelper.lib joinscript_init . /usr/share/univention-lib/ldap.sh ucs_unregisterLDAPExtension "$@" --udm_%(extension_type)s %(extension_name)s joinscript_remove_script_from_status_file %(package_name)s exit 0 ''' % {'package_name': package_name, 'extension_name': extension_name, 'extension_type': extension_type}
[docs] def get_absolute_extension_filename(extension_type: str, filename: str) -> str: """Returns the absolute path to an extentension of the given type and filename.""" assert extension_type in VALID_EXTENSION_TYPES if extension_type == 'module': assert '/' in filename return '/usr/lib/python3/dist-packages%s' % ({ 'hook': '/univention/admin/hooks.d/%s', 'syntax': '/univention/admin/syntax.d/%s', 'module': '/univention/admin/handlers/%s', }[extension_type] % filename)
[docs] def get_dn_of_extension_by_name(extension_type: str, name: str) -> str: """Returns a list of DNs of UDM extension objects with given type an name, or [] if no object has been found.""" assert extension_type in VALID_EXTENSION_TYPES if extension_type == 'module': assert '/' in name searchfilter = { 'hook': '(&(objectClass=univentionUDMHook)(cn=%s))' % name, 'syntax': '(&(objectClass=univentionUDMSyntax)(cn=%s))' % name, 'module': '(&(objectClass=univentionUDMModule)(cn=%s))' % name, }[extension_type] return get_ldap_connection().searchDn(filter=searchfilter)
[docs] def remove_extension_by_name(extension_type: str, extension_name: str, fail_on_error: bool = True) -> None: """Remove all extensions of given type and name from LDAP.""" assert extension_type in VALID_EXTENSION_TYPES for dn in get_dn_of_extension_by_name(extension_type, extension_name): cmd = ['/usr/sbin/udm-test', 'settings/udm_%s' % extension_type, 'remove', '--dn', dn] print('CMD: %r' % cmd) sys.stdout.flush() if subprocess.call(cmd): if fail_on_error: fail('Failed to remove %s' % dn) else: print('ERROR: failed to remove %s' % dn)