From 5493b8686de9c88975aed07ec6ca9a85f6cd1b76 Mon Sep 17 00:00:00 2001 From: Simon Pichugin Date: Nov 16 2017 13:26:47 +0000 Subject: Issue lib389 #77 - Refactor docstrings in rST format - part 2 Description: Improve docstring coverage for IDM modules. Fix small typos in replica, backend and mappingTree modules. Add Python 3 support chapter to guidelines.rst. Fix links to the source code because of the lib389 repo merging. https://pagure.io/lib389/issue/77 Reviewed by: mreynolds, wibrown (Thanks!) --- diff --git a/src/lib389/doc/source/backend.rst b/src/lib389/doc/source/backend.rst index 5b4e1f4..94cdae8 100644 --- a/src/lib389/doc/source/backend.rst +++ b/src/lib389/doc/source/backend.rst @@ -8,33 +8,13 @@ Usage example from lib389.backend import Backends backends = Backends(standalone) - backend = backends.create(properties={BACKEND_SUFFIX: 'o=new_suffix', # mandatory - BACKEND_NAME: new_backend, # mandatory - BACKEND_SAMPLE_ENTRIES: '001003006'}) + backend = backends.create(properties={'nsslapd-suffix': 'o=new_suffix', # mandatory + 'cn': 'new_backend'}) # mandatory # Create sample entries backend.create_sample_entries(version='001003006') backend.delete() - -Backend properties -------------------- - -- BACKEND_NAME - 'somename' -- BACKEND_READONLY - 'on' | 'off' -- BACKEND_REQ_INDEX - 'on' | 'off' -- BACKEND_CACHE_ENTRIES - 1 to (2^32 - 1) on 32-bit systems or (2^63 - 1) - on 64-bit systems or -1, which means limitless -- BACKEND_CACHE_SIZE - 500 kilobytes to (2^32 - 1) - on 32-bit systems and to (2^63 - 1) on 64-bit systems -- BACKEND_DNCACHE_SIZE - 500 kilobytes to (2^32 - 1) - on 32-bit systems and to (2^63 - 1) on 64-bit systems -- BACKEND_DIRECTORY - Any valid path to the database instance -- BACKEND_CHAIN_BIND_DN - DN of the multiplexor -- BACKEND_CHAIN_BIND_PW - password of the multiplexor -- BACKEND_CHAIN_URLS - Any valid remote server LDAP URL -- BACKEND_SUFFIX - 'o=somesuffix' -- BACKEND_SAMPLE_ENTRIES - version of confir i.e. '001003006' Module documentation diff --git a/src/lib389/doc/source/conf.py b/src/lib389/doc/source/conf.py index c645db2..b9c9aec 100644 --- a/src/lib389/doc/source/conf.py +++ b/src/lib389/doc/source/conf.py @@ -14,6 +14,7 @@ import sys import os +import inspect # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the @@ -294,10 +295,6 @@ texinfo_documents = [ # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = {'https://docs.python.org/': None} -import lib389 -import inspect -from os.path import relpath, dirname - def linkcode_resolve(domain, info): if domain != 'py': return None @@ -306,7 +303,6 @@ def linkcode_resolve(domain, info): module_name = info['module'] fullname = info['fullname'] - file_name = module_name.replace('.', '/') # Get the module object submodule = sys.modules.get(module_name) @@ -321,6 +317,14 @@ def linkcode_resolve(domain, info): except: return None + # inspect.getsourcelines doesn't support data descriptors + # Probably we can get the source another way, for now it's okay to skip + if isinstance(next_submodule, property): + return None + + file_path = inspect.getsourcefile(next_submodule) + file_name = file_path.split('lib389/lib389/')[-1] + # Get line number for the submodule try: _, line = inspect.getsourcelines(next_submodule) @@ -332,4 +336,4 @@ def linkcode_resolve(domain, info): else: line_fmt = "" - return "https://pagure.io/lib389/blob/master/f/{}.py{}".format(file_name, line_fmt) + return "https://pagure.io/389-ds-base/blob/master/f/src/lib389/lib389/{}{}".format(file_name, line_fmt) diff --git a/src/lib389/doc/source/guidelines.rst b/src/lib389/doc/source/guidelines.rst index 6d80c11..0d67218 100644 --- a/src/lib389/doc/source/guidelines.rst +++ b/src/lib389/doc/source/guidelines.rst @@ -1,8 +1,7 @@ ============================================================ Guidelines for using pytest and lib389 ============================================================ -The guide covers basic workflow with git, py.test, lib389, python-ldap -and 1minutetip CLIs. +The guide covers basic workflow with git, py.test, lib389 and python-ldap. For a saving place purposes, I'll replace topology_m2.ms["master1"] with master1 , etc. @@ -347,6 +346,19 @@ Asserting assert 'maximum recursion' in str(excinfo.value) +Python 3 support +================ + +Our project should support Python 3. Python-ldap works with 'byte' strings only. +So we should use lib389 functions as much as possible because they take care of this issue. + +If you still must use 'modify_s', 'add_s' or other python-ldap functions, you should consider defining the attribute as 'byte'. You can do this like this, with b'' symbol: + +:: + + # Modify an entry + standalone.modify_s(USER_DN, [(ldap.MOD_REPLACE, 'cn', b'Mark Reynolds')]) + Constants ========== @@ -395,14 +407,15 @@ by DSLdapObjects. # Add an entry USER_DN = 'cn=mreynolds,{}'.format(DEFAULT_SUFFIX) standalone.add_s(Entry((USER_DN, { - 'objectclass': 'top person'.split(), - 'cn': 'mreynolds', - 'sn': 'reynolds', - 'userpassword': 'password' + 'objectclass': b'top', + 'objectclass': b'person', + 'cn': b'mreynolds', + 'sn': b'reynolds', + 'userpassword': b'password' }))) # Modify an entry - standalone.modify_s(USER_DN, [(ldap.MOD_REPLACE, 'cn', 'Mark Reynolds')]) + standalone.modify_s(USER_DN, [(ldap.MOD_REPLACE, 'cn', b'Mark Reynolds')]) # Delete an entry standalone.delete_s(USER_DN) @@ -595,5 +608,5 @@ Basic configuration # Initialize the agreement, wait for it complete, and test that replication is really working standalone.agreement.init(DEFAULT_SUFFIX, master2.host, master2.port) - standalone.waitForReplInit(repl_agreement) - assert standalone.testReplication(DEFAULT_SUFFIX, master2) + replica.start_and_wait(repl_agreement) + assert replicas.test(master2) diff --git a/src/lib389/doc/source/index.rst b/src/lib389/doc/source/index.rst index e5445f8..2d77459 100644 --- a/src/lib389/doc/source/index.rst +++ b/src/lib389/doc/source/index.rst @@ -23,13 +23,13 @@ Contents Replication Configuring Databases Access Control + Identity Management Work in progress ----------------- .. toctree:: :maxdepth: 2 - Identity Management need_to_be_triaged.rst diff --git a/src/lib389/doc/source/replica.rst b/src/lib389/doc/source/replica.rst index 75acdb8..3d6c4d4 100644 --- a/src/lib389/doc/source/replica.rst +++ b/src/lib389/doc/source/replica.rst @@ -13,11 +13,15 @@ Usage example # - replica manager will be with the defaults # - replica.create() will be executed replica = replicas.enable(suffix=DEFAULT_SUFFIX, - role=REPLICAROLE_MASTER, + role=ReplicaRole.MASTER, replicaID=REPLICAID_MASTER_1) - # Roles - REPLICAROLE_MASTER, REPLICAROLE_HUB, and REPLICAROLE_CONSUMER + + # Or you can get it as usual DSLdapObject + replica = replicas.list()[0] + + # Roles - ReplicaRole.MASTER, ReplicaRole.HUB, and ReplicaRole.CONSUMER # For masters and hubs you can use the constants REPLICAID_MASTER_X and REPLICAID_HUB_X - # Change X for a number from 1 to 100 - for role REPLICAROLE_MASTER only + # Change X for a number from 1 to 100 - for role ReplicaRole.MASTER only # Disable replication # - agreements and replica entry will be deleted @@ -32,12 +36,12 @@ Usage example # Promote replicas.promote(suffix=DEFAULT_SUFFIX, - newrole=REPLICAROLE_MASTER, + newrole=ReplicaRole.MASTER, binddn=REPL_BINDDN, rid=REPLICAID_MASTER_1) # Demote replicas.demote(suffix=DEFAULT_SUFFIX, - newrole=REPLICAROLE_CONSUMER) + newrole=ReplicaRole.CONSUMER) # Test, that replication works replicas.test(master2) diff --git a/src/lib389/doc/source/user.rst b/src/lib389/doc/source/user.rst index 6d59e91..2ed9b4c 100644 --- a/src/lib389/doc/source/user.rst +++ b/src/lib389/doc/source/user.rst @@ -7,6 +7,7 @@ Usage example # There is a basic way to work with it from lib389.idm.user import UserAccounts + users = UserAccounts(standalone, DEFAULT_SUFFIX) user_properties = { 'uid': USER_NAME, @@ -45,6 +46,8 @@ Module documentation .. autoclass:: lib389.idm.user.UserAccounts :members: + :inherited-members: .. autoclass:: lib389.idm.user.UserAccount :members: + :inherited-members: diff --git a/src/lib389/lib389/_mapped_object.py b/src/lib389/lib389/_mapped_object.py index 4e5bc72..b78d8a4 100644 --- a/src/lib389/lib389/_mapped_object.py +++ b/src/lib389/lib389/_mapped_object.py @@ -82,7 +82,7 @@ class DSLogging(object): class DSLdapObject(DSLogging): """A single instance of DSLdapObjects - :param instance: A instance + :param instance: An instance :type instance: lib389.DirSrv :param dn: Entry DN :type dn: str @@ -718,7 +718,7 @@ class DSLdapObjects(DSLogging): that exists in this way", i.e. we unite LDAP entries by some set of parameters with the object. - :param instance: A instance + :param instance: An instance :type instance: lib389.DirSrv :param batch: Not implemented :type batch: bool diff --git a/src/lib389/lib389/backend.py b/src/lib389/lib389/backend.py index 292bf03..5f78d41 100644 --- a/src/lib389/lib389/backend.py +++ b/src/lib389/lib389/backend.py @@ -387,10 +387,10 @@ class BackendLegacy(object): class Backend(DSLdapObject): """Backend DSLdapObject with: - - must attributes = ['cn, 'nsslapd-suffix'] + - must attributes = ['cn', 'nsslapd-suffix'] - RDN attribute is 'cn' - :param instance: A instance + :param instance: An instance :type instance: lib389.DirSrv :param dn: Entry DN :type dn: str @@ -566,7 +566,7 @@ class Backends(DSLdapObjects): This only does ldbm backends. Chaining backends are a special case of this, so they can be subclassed off. - :param instance: A instance + :param instance: An instance :type instance: lib389.DirSrv :param batch: Not implemented :type batch: bool diff --git a/src/lib389/lib389/idm/account.py b/src/lib389/lib389/idm/account.py index 02e1874..8dcbe6b 100644 --- a/src/lib389/lib389/idm/account.py +++ b/src/lib389/lib389/idm/account.py @@ -10,17 +10,45 @@ from lib389._mapped_object import DSLdapObject, DSLdapObjects, _gen_or, _gen_fil class Account(DSLdapObject): + """A single instance of Account entry + + :param instance: An instance + :type instance: lib389.DirSrv + :param dn: Entry DN + :type dn: str + :param batch: Not implemented + :type batch: bool + """ + def is_locked(self): - # Check if nsAccountLock is set. + """Check if nsAccountLock is set + + :returns: True if account is locked + """ + return self.present('nsAccountLock') def lock(self): + """Set nsAccountLock to 'true'""" + self.replace('nsAccountLock', 'true') def unlock(self): + """Unset nsAccountLock""" + self.remove('nsAccountLock', None) class Accounts(DSLdapObjects): + """DSLdapObjects that represents Account entry + + :param instance: An instance + :type instance: lib389.DirSrv + :param basedn: Base DN for all account entries below + :type basedn: str + :param batch: Not implemented + :type batch: bool + """ + def __init__(self, instance, basedn, batch=False): super(Accounts, self).__init__(instance, batch) # These are all the objects capable of holding a password. diff --git a/src/lib389/lib389/idm/domain.py b/src/lib389/lib389/idm/domain.py index 480c1c8..a30c5bd 100644 --- a/src/lib389/lib389/idm/domain.py +++ b/src/lib389/lib389/idm/domain.py @@ -6,9 +6,21 @@ # See LICENSE for details. # --- END COPYRIGHT BLOCK --- -from lib389._mapped_object import DSLdapObject, DSLdapObjects +from lib389._mapped_object import DSLdapObject class Domain(DSLdapObject): + """A single instance of Domain entry + - must attributes = ['dc'] + - RDN attribute is 'dc' + + :param instance: An instance + :type instance: lib389.DirSrv + :param dn: Entry DN + :type dn: str + :param batch: Not implemented + :type batch: bool + """ + def __init__(self, instance, dn=None, batch=False): super(Domain, self).__init__(instance, dn, batch) self._rdn_attribute = 'dc' diff --git a/src/lib389/lib389/idm/group.py b/src/lib389/lib389/idm/group.py index 5de1294..be6001c 100644 --- a/src/lib389/lib389/idm/group.py +++ b/src/lib389/lib389/idm/group.py @@ -15,6 +15,16 @@ MUST_ATTRIBUTES = [ RDN = 'cn' class Group(DSLdapObject): + """A single instance of Group entry + + :param instance: An instance + :type instance: lib389.DirSrv + :param dn: Entry DN + :type dn: str + :param batch: Not implemented + :type batch: bool + """ + def __init__(self, instance, dn=None, batch=False): super(Group, self).__init__(instance, dn, batch) self._rdn_attribute = RDN @@ -29,16 +39,45 @@ class Group(DSLdapObject): self._protected = False def is_member(self, dn): - # Check if dn is a member + """Check if DN is a member + + :param dn: Entry DN + :type dn: str + """ + return self.present('member', dn) def add_member(self, dn): + """Add DN as a member + + :param dn: Entry DN + :type dn: str + """ + self.add('member', dn) def remove_member(self, dn): + """Remove a member with specified DN + + :param dn: Entry DN + :type dn: str + """ + self.remove('member', dn) + class Groups(DSLdapObjects): + """DSLdapObjects that represents Groups entry + By default it uses 'ou=Groups' as rdn. + + :param instance: An instance + :type instance: lib389.DirSrv + :param basedn: Base DN for all group entries below + :type basedn: str + :param batch: Not implemented + :type batch: bool + """ + def __init__(self, instance, basedn, batch=False, rdn='ou=Groups'): super(Groups, self).__init__(instance, batch) self._objectclasses = [ @@ -48,6 +87,7 @@ class Groups(DSLdapObjects): self._childobject = Group self._basedn = '{},{}'.format(ensure_str(rdn), ensure_str(basedn)) + class UniqueGroup(DSLdapObject): # WARNING!!! # Use group, not unique group!!! @@ -73,6 +113,7 @@ class UniqueGroup(DSLdapObject): def remove_member(self, dn): self.remove('uniquemember', dn) + class UniqueGroups(DSLdapObjects): # WARNING!!! # Use group, not unique group!!! diff --git a/src/lib389/lib389/idm/organisationalrole.py b/src/lib389/lib389/idm/organisationalrole.py index 05b2292..7bfb2f7 100644 --- a/src/lib389/lib389/idm/organisationalrole.py +++ b/src/lib389/lib389/idm/organisationalrole.py @@ -15,6 +15,16 @@ RDN = 'cn' class OrganisationalRole(DSLdapObject): + """A single instance of OrganizationalRole entry + + :param instance: An instance + :type instance: lib389.DirSrv + :param dn: Entry DN + :type dn: str + :param batch: Not implemented + :type batch: bool + """ + def __init__(self, instance, dn=None, batch=False): super(OrganisationalRole, self).__init__(instance, dn, batch) self._rdn_attribute = RDN @@ -27,6 +37,16 @@ class OrganisationalRole(DSLdapObject): class OrganisationalRoles(DSLdapObjects): + """DSLdapObjects that represents OrganizationalRole entry + + :param instance: An instance + :type instance: lib389.DirSrv + :param basedn: Base DN for all group entries below + :type basedn: str + :param batch: Not implemented + :type batch: bool + """ + def __init__(self, instance, basedn, batch=False): super(OrganisationalRoles, self).__init__(instance, batch) self._objectclasses = [ diff --git a/src/lib389/lib389/idm/organisationalunit.py b/src/lib389/lib389/idm/organisationalunit.py index a3854c9..8a29970 100644 --- a/src/lib389/lib389/idm/organisationalunit.py +++ b/src/lib389/lib389/idm/organisationalunit.py @@ -14,6 +14,16 @@ MUST_ATTRIBUTES = [ RDN = 'ou' class OrganisationalUnit(DSLdapObject): + """A single instance of OrganizationalUnit entry + + :param instance: An instance + :type instance: lib389.DirSrv + :param dn: Entry DN + :type dn: str + :param batch: Not implemented + :type batch: bool + """ + def __init__(self, instance, dn=None, batch=False): super(OrganisationalUnit, self).__init__(instance, dn, batch) self._rdn_attribute = RDN @@ -26,6 +36,16 @@ class OrganisationalUnit(DSLdapObject): self._protected = False class OrganisationalUnits(DSLdapObjects): + """DSLdapObjects that represents OrganizationalUnits entry + + :param instance: An instance + :type instance: lib389.DirSrv + :param basedn: Base DN for all group entries below + :type basedn: str + :param batch: Not implemented + :type batch: bool + """ + def __init__(self, instance, basedn, batch=False): super(OrganisationalUnits, self).__init__(instance, batch) self._objectclasses = [ diff --git a/src/lib389/lib389/idm/posixgroup.py b/src/lib389/lib389/idm/posixgroup.py index 8605f63..60a2d56 100644 --- a/src/lib389/lib389/idm/posixgroup.py +++ b/src/lib389/lib389/idm/posixgroup.py @@ -16,6 +16,16 @@ MUST_ATTRIBUTES = [ RDN = 'cn' class PosixGroup(DSLdapObject): + """A single instance of PosixGroup entry + + :param instance: An instance + :type instance: lib389.DirSrv + :param dn: Entry DN + :type dn: str + :param batch: Not implemented + :type batch: bool + """ + def __init__(self, instance, dn=None, batch=False): super(PosixGroup, self).__init__(instance, dn, batch) self._rdn_attribute = RDN @@ -31,14 +41,37 @@ class PosixGroup(DSLdapObject): self._protected = False def check_member(self, dn): + """Check if DN is a member + + :param dn: Entry DN + :type dn: str + """ + return dn in self.get_attr_vals('member') def add_member(self, dn): + """Add DN as a member + + :param dn: Entry DN + :type dn: str + """ + # Assert the DN exists? self.add('member', dn) class PosixGroups(DSLdapObjects): + """DSLdapObjects that represents PosixGroups entry + By default it uses 'ou=Groups' as rdn. + + :param instance: An instance + :type instance: lib389.DirSrv + :param basedn: Base DN for all group entries below + :type basedn: str + :param batch: Not implemented + :type batch: bool + """ + def __init__(self, instance, basedn, batch=False, rdn='ou=Groups'): super(PosixGroups, self).__init__(instance, batch) self._objectclasses = [ diff --git a/src/lib389/lib389/idm/services.py b/src/lib389/lib389/idm/services.py index 129dad2..54b6808 100644 --- a/src/lib389/lib389/idm/services.py +++ b/src/lib389/lib389/idm/services.py @@ -14,6 +14,16 @@ MUST_ATTRIBUTES = [ ] class ServiceAccount(DSLdapObject): + """A single instance of Service entry + + :param instance: An instance + :type instance: lib389.DirSrv + :param dn: Entry DN + :type dn: str + :param batch: Not implemented + :type batch: bool + """ + def __init__(self, instance, dn=None, batch=False): super(ServiceAccount, self).__init__(instance, dn, batch) self._rdn_attribute = RDN @@ -25,6 +35,17 @@ class ServiceAccount(DSLdapObject): self._protected = False class ServiceAccounts(DSLdapObjects): + """DSLdapObjects that represents Services entry + By default it uses 'ou=Services' as rdn. + + :param instance: An instance + :type instance: lib389.DirSrv + :param basedn: Base DN for all group entries below + :type basedn: str + :param batch: Not implemented + :type batch: bool + """ + def __init__(self, instance, basedn, batch=False, rdn='ou=Services'): super(ServiceAccounts, self).__init__(instance, batch) self._objectclasses = [ diff --git a/src/lib389/lib389/idm/user.py b/src/lib389/lib389/idm/user.py index 5c3f55f..10b787a 100644 --- a/src/lib389/lib389/idm/user.py +++ b/src/lib389/lib389/idm/user.py @@ -32,6 +32,16 @@ TEST_USER_PROPERTIES = { class UserAccount(Account): + """A single instance of User Account entry + + :param instance: An instance + :type instance: lib389.DirSrv + :param dn: Entry DN + :type dn: str + :param batch: Not implemented + :type batch: bool + """ + def __init__(self, instance, dn=None, batch=False): super(UserAccount, self).__init__(instance, dn, batch) self._rdn_attribute = RDN @@ -70,7 +80,21 @@ class UserAccount(Account): # Add a set password function.... # Can't I actually just set, and it will hash? + class UserAccounts(DSLdapObjects): + """DSLdapObjects that represents all User Account entries in suffix. + By default it uses 'ou=People' as rdn. + + :param instance: An instance + :type instance: lib389.DirSrv + :param basedn: Suffix DN + :type basedn: str + :param rdn: The DN that will be combined wit basedn + :type rdn: str + :param batch: Not implemented + :type batch: bool + """ + def __init__(self, instance, basedn, batch=False, rdn='ou=People'): super(UserAccounts, self).__init__(instance, batch) self._objectclasses = [ diff --git a/src/lib389/lib389/mappingTree.py b/src/lib389/lib389/mappingTree.py index 2796e22..a7ef3e8 100644 --- a/src/lib389/lib389/mappingTree.py +++ b/src/lib389/lib389/mappingTree.py @@ -383,7 +383,7 @@ class MappingTree(DSLdapObject): - must attributes = ['cn'] - RDN attribute is 'cn' - :param instance: A instance + :param instance: An instance :type instance: lib389.DirSrv :param dn: Entry DN :type dn: str @@ -418,7 +418,7 @@ class MappingTree(DSLdapObject): class MappingTrees(DSLdapObjects): """DSLdapObjects that presents Mapping trees - :param instance: A instance + :param instance: An instance :type instance: lib389.DirSrv :param batch: Not implemented :type batch: bool diff --git a/src/lib389/lib389/replica.py b/src/lib389/lib389/replica.py index 48cbcd7..48b879f 100644 --- a/src/lib389/lib389/replica.py +++ b/src/lib389/lib389/replica.py @@ -1111,8 +1111,11 @@ class Replica(DSLdapObject): def start_and_wait(self, agmtdn): """Initialize an agreement and wait for it to complete - @param agmtdn - agreement dn - @return - 0 if successful + :param agmtdn: The agreement DN + :type agmtdn: str + + :returns: 0 if the initialization is complete + THIS SHOULD BE IN THE NEW AGREEMENT CLASS """ @@ -1124,7 +1127,7 @@ class Replica(DSLdapObject): return rc def start_async(self, agmtdn): - """Initialize replication without waiting. + """Initialize replication without waiting :param agmtdn: The agreement DN :type agmtdn: str