Browse Source

Update documentation

master
Joshua Rubingh 7 months ago
parent
commit
4970aa9fa6
  1. 294
      agent.py
  2. 33
      doc/agent.rst
  3. 3
      doc/conf.py
  4. BIN
      doc/documentation.pdf
  5. 1
      doc/index.rst

294
agent.py

@ -14,38 +14,104 @@ import os
WORKINGDIR, _ = os.path.split(os.path.abspath(__file__))
class LDAPError(Exception):
"""General LDAP error exception
Args:
message (string): Error message
"""
def __init__(self, message):
super().__init__(message)
class DuplicateResearchGroup(Exception):
"""Duplicate researchgroup exception
The researchgroup that is being added does already exists in the LDAP server
Args:
message (string): Error message
"""
def __init__(self, message):
super().__init__(message)
class ResearchGroupDoesNotExists(Exception):
"""Researchgroup does not exists exception
The requested researchgroup does not exists in the LDAP server
Args:
message (string): Error message
"""
def __init__(self, message):
super().__init__(message)
class DuplicateResearcher(Exception):
"""Duplicate researcher exception
The researcher that is being added does already exists in the LDAP server
Args:
message (string): Error message
"""
def __init__(self, message):
super().__init__(message)
class ResearcherDoesNotExists(Exception):
"""Researcher does not exists exception
The requested researcher does not exists in the LDAP server
Args:
message (string): Error message
"""
def __init__(self, message):
super().__init__(message)
class VRWLDAP():
"""This is the VRW LDAP client. With this client you can make changes in the VRW LDAP server. This includes adding, updating and deleting of researchgroups and researchers.
Args:
host (string): The hostname or IP of the LDAP server
port (string): The portnumber of the LDAP server
login (string): The login name for the LDAP server
password (string): The password for the LDAP server
ssl (bool, optional): Use SSL for the connection. Defaults to False.
Returns:
VRWLDAP: LDAP client
"""
__RESOURCE_GROUPS_BASE = 'ou=researchgroups,ou=dfh,o=co'
__RESOURCE_MEMBERS_BASE = 'ou=users,ou=dfh,o=co'
def __init__(self, host, port, login, password, ssl = False):
"""Create a new VRW LDAP client
Args:
host (string): The hostname or IP of the LDAP server
port (string): The portnumber of the LDAP server
login (string): The login name for the LDAP server
password (string): The password for the LDAP server
ssl (bool, optional): Use SSL for the connection. Defaults to False.
"""
self.server = Server(host, port=port, use_ssl=ssl, get_info=ALL)
self.connection = Connection(self.server, login, password)
def __find_next_group_id(self):
"""Helper function. This will return a new group ID number that can be used for creating researchgroups
It will search for the current max value of field 'hsrGroupGID' in all the existing researchgroups and add 1 to that value.
Returns:
int: The new group ID that can be used
"""
_field = 'hsrGroupGID'
group_id = 0
self.connection.search(self.__RESOURCE_GROUPS_BASE,f'({_field}=*)', attributes=[_field])
@ -56,6 +122,13 @@ class VRWLDAP():
return group_id + 1
def __find_next_researcher_id(self):
"""Helper function. This will return a new researcher ID number that can be used for creating researchers
It will search for the current max value of field 'hsrUserUID' in all the existing researchers and add 1 to that value.
Returns:
int: The new researcher ID that can be used
"""
_field = 'hsrUserUID'
researcher_id = 0
self.connection.search(self.__RESOURCE_MEMBERS_BASE,f'({_field}=*)', attributes=[_field])
@ -66,9 +139,26 @@ class VRWLDAP():
return researcher_id + 1
def check_connection(self):
"""Check if the LDAP connection is successfull
Returns:
bool: True when the connection is successfull, else False
"""
return self.connection.bind()
def search_research_group(self, name, id = None):
"""Find the LDAP DN path for a researchgroup.
It can search both on name and existing ID. When the 'id' parameter is set (not None), it will do first a LDAP DN key lookup. If that fails, if will fallback on the 'name' parameter.
If the 'id' parameter is None, there will be only searched on the name.
Args:
name (string): The researchgroup name
id (string, optional): Find the researchgroup based on ID. Defaults to None.
Returns:
string, bool: Return the LDAP DN name when found or False when not found
"""
search_base = self.__RESOURCE_GROUPS_BASE
search_filter = f'(ou={slugify(name)})'
@ -91,6 +181,20 @@ class VRWLDAP():
return False
def create_research_group(self, name, subgroups = False):
"""Create a new research group in the LDAP server based on the name.
The name will be changed to strip if from special characters and spaces. The name will bug slugified.
If will return the newly created researchgroup DN key.
Raises:
LDAPError: When there is a general LDAP error
DuplicateResearchGroup: When the researchgroup already exists in the LDAP server
Args:
name (string): The researchgroup name
subgroups (bool, optional): Create the needed subgroups in LDAP. Defaults to False.
"""
def __create_sub_groups(group_name, parent_dn, start_id):
sub_groups = [f'{group_name}:Members',
@ -139,6 +243,21 @@ class VRWLDAP():
raise DuplicateResearchGroup(f'Researchgroup {name} already exists!')
def update_research_group(self, group_dn, name):
"""Update an existing researchgroup.
This will change the name of the researchgroup, and make sure that all the references in the LDAP to this researchgroup are updated.
Args:
group_dn (string): The researchgroup LDAP DN key
name (string): The new name
Raises:
LDAPError: When there is a general LDAP error
ResearchGroupDoesNotExists: When the researchgroup does not exists in the LDAP server
Returns:
string: The new researchgroup LDAP DN key
"""
# Find the existing research group based on DN and then its name
research_group = self.search_research_group(name, group_dn)
if research_group is not False:
@ -168,6 +287,16 @@ class VRWLDAP():
def delete_research_group(self, group_dn):
"""Delete an existing researchgroup from the LDAP server based on DN key
This will also update the LDAP references of the researchers if they where member of this researchgroup.
Args:
group_dn (string): The LDAP DN key to be deleted
Raises:
ResearchGroupDoesNotExists: When the researchgroup does not exists in the LDAP server
"""
logging.debug(f'Delete research group "{group_dn}"')
if not self.search_research_group('', group_dn):
raise ResearchGroupDoesNotExists(f'Invalid research group DN: {group_dn}')
@ -183,6 +312,18 @@ class VRWLDAP():
self.connection.delete(group_dn)
def research_group_members(self, group_dn):
"""Get all the researchers from a researchgroup
Args:
group_dn (string): The researchgroup LDAP DN key
Raises:
ResearchGroupDoesNotExists: When the researchgroup does not exists in the LDAP server
Returns:
list (string): Returns a list with all the member DNs
"""
logging.debug(f'Getting all researchers for group "{group_dn}"')
members = []
@ -197,6 +338,14 @@ class VRWLDAP():
return members
def search_researcher(self, email):
"""Find a reseacher based on its email address.
Args:
email (string): The email address of the researcher to find
Returns:
string, bool: Return the LDAP DN key when found or False when not found
"""
logging.debug(f'Searching for research member "{email}" in DN "{self.__RESOURCE_MEMBERS_BASE}"')
if self.connection.search(self.__RESOURCE_MEMBERS_BASE,f'(uid={email})'):
if len(self.connection.entries) == 1:
@ -207,6 +356,22 @@ class VRWLDAP():
return False
def create_researcher(self, firstname, lastname, email, mobile, pnumber):
"""Create a new researcher.
Args:
firstname (string): Firstname of the researcher
lastname (string): Middle and lastname of the researcher
email (string): Email address of the researcher
mobile (string): Mobile number used for MFA.
pnumber (string): Researcher ID number at the univeristy
Raises:
LDAPError: When there is a general LDAP error
DuplicateResearcher: When the researcher does already exists
Returns:
[type]: [description]
"""
researcher = self.search_researcher(email)
if not researcher:
logging.info(f'Creating a new researcher ({firstname} {lastname}) with the email address "{email}"')
@ -237,24 +402,50 @@ class VRWLDAP():
raise DuplicateResearcher(f'Researcher {firstname} {lastname} - {email} already exists!')
def update_researcher(self, user_dn, firstname, lastname, email, mobile, pnumber):
def update_researcher(self, firstname, lastname, email, mobile, pnumber):
"""Update an existing researcher with new data. Email address is used as unique key
Args:
firstname (string): Firstname of the researcher
lastname (string): Middle and lastname of the researcher
email (string): Email address of the researcher
mobile (string): Mobile number used for MFA.
pnumber (string): Researcher ID number at the univeristy
Raises:
LDAPError: When there is a general LDAP error
Returns:
string: The researcher LDAP DN key
"""
researcher = self.search_researcher(email)
if researcher is not False:
update_ok = self.connection.modify(user_dn, {'cn' : [(MODIFY_REPLACE, [f'{firstname} {lastname}'])],
'sn' : [(MODIFY_REPLACE, [lastname])],
'displayName' : [(MODIFY_REPLACE, [f'{firstname} {lastname}'])],
'givenName' : [(MODIFY_REPLACE, [firstname])],
'employeeNumber' : [(MODIFY_REPLACE, [pnumber])],
'description' : [(MODIFY_REPLACE, [f'Researcher {firstname} {lastname}'])],
'mobile' : [(MODIFY_REPLACE, [mobile])] })
update_ok = self.connection.modify(researcher, {'cn' : [(MODIFY_REPLACE, [f'{firstname} {lastname}'])],
'sn' : [(MODIFY_REPLACE, [lastname])],
'displayName' : [(MODIFY_REPLACE, [f'{firstname} {lastname}'])],
'givenName' : [(MODIFY_REPLACE, [firstname])],
'employeeNumber' : [(MODIFY_REPLACE, [pnumber])],
'description' : [(MODIFY_REPLACE, [f'Researcher {firstname} {lastname}'])],
'mobile' : [(MODIFY_REPLACE, [mobile])] })
if update_ok:
logging.info(f'Updated researcher ({firstname} {lastname}) with the email address "{email}"')
return user_dn
return researcher
raise LDAPError(f'Could not update the researcher {firstname} {lastname} - {email}!')
def researcher_to_group(self, group_dn, researcher_dn, role = None, workstation = None):
"""Add a researcher to a researchgroup. And add roles and a workstation for this researcher is needed.
Args:
group_dn (string): The researchgroup LDAP DN key
researcher_dn (string): The researcher LDAP DN key
role (string, optional): The role for this researcher in this group. Defaults to None.
workstation (string, optional): The workstation for this researcher in this group. Defaults to None.
Returns:
tuple: Returns a tuple with the groupmember LDAP DN key and the workstation LDAP DN key
"""
regex = r"^ou=(?P<group_name>[^,]+)"
matches = re.search(regex, group_dn)
if matches:
@ -286,6 +477,12 @@ class VRWLDAP():
def remove_researcher_from_group(self, group_dn, researcher_dn):
"""Remove a researcher from a researchgroup. This will also cleanup all LDAP references between researcher and researchgroup
Args:
group_dn (string): The LDAP DN key of the researchgroup
researcher_dn (string): The LDAP DN key of the researcher
"""
logging.info(f'Removing researcher {researcher_dn} from group {group_dn}')
if self.connection.search(researcher_dn,'(objectclass=person)', attributes=['groupMembership']) and len(self.connection.entries) == 1:
logging.debug(f'Found researcher {researcher_dn} with {len(self.connection.entries[0]["groupMembership"])} memberships.')
@ -297,8 +494,30 @@ class VRWLDAP():
class VRE_API_CLient():
"""This is the VRE API client. With this client you can easyly get the latest VRW actions from the VRE API.
Args:
base_url (string): The full url to the base API. Including protocol and optionally port nunber
api_prefix (string): The current API version
username (string): The login name for the VRE API server
password (string): The password for the VRE API server
Returns:
VRE_API_CLient: VRE API client
"""
def __init__(self, base_url, api_prefix, username, password):
"""This is the VRE API client. With this client you can easyly get the latest VRW actions from the VRE API.
Args:
base_url (string): The full url to the base API. Including protocol and optionally port nunber
api_prefix (string): The current API version
username (string): The login name for the VRE API server
password (string): The password for the VRE API server
Returns:
VRE_API_CLient: VRE API client
"""
self.base_url = base_url.strip('/')
self.username = username
self.password = password
@ -307,9 +526,19 @@ class VRE_API_CLient():
self.__authorization_header = None
def __get_full_url(self, part):
"""Helperfunction: This will construct a full url to the API endpoint.
Args:
part (string): The endpoint location
Returns:
string: The full url including protocol and complete path.
"""
return f'{self.base_url}/{self.api_prefix.strip("/")}/{part.lstrip("/")}'.replace(f'/{self.api_prefix.strip("/")}/auth/','/auth/')
def __get_JWT_token(self):
"""Helperfunction: This will get a new JWT token from the VRE API server based on the user login.
"""
self.__authorization_header = None
login = requests.post(self.__get_full_url('/auth/jwt/create/'), json={
'username' : self.username,
@ -362,10 +591,20 @@ class VRE_API_CLient():
return online_data.json()
def check_connection(self):
"""Check if the VRE API connection is successfull
Returns:
bool: True when the connection is successfull, else False
"""
data = self._get_data('/auth/users/me/')
return False if data is None else data.get('username') == self.username
def get_new_workspaces(self):
"""Get a list of new workstations to make
Returns:
list(dict): A list of dicts with new workstations to make
"""
data = self._get_data('/vrw/list/new/')
workspaces = []
@ -375,6 +614,11 @@ class VRE_API_CLient():
return workspaces
def get_changing_workspaces(self):
"""Get a list of workstations to update
Returns:
list(dict): A list of dicts with workstations to be updated
"""
data = self._get_data('/vrw/list/change/')
workspaces = []
@ -384,6 +628,11 @@ class VRE_API_CLient():
return workspaces
def get_deleted_workspaces(self):
"""Get a list of workstations to delete
Returns:
list(dict): A list of dicts with workstations to be deleted
"""
data = self._get_data('/vrw/list/delete/')
workspaces = []
for workspace in data['results']:
@ -392,10 +641,22 @@ class VRE_API_CLient():
return workspaces
def workspace_done(self, id, cloud_id):
"""Update back to the VRE API that a workstation has been created. Add a cloud id for reference.
Args:
id (int): The VRE API workstation ID
cloud_id (string): The cloud id that is created with this workstation
"""
data = self._put_data(f'vrw/{id}/status/', data={'status' : 'DONE', 'cloud_id' : cloud_id})
print(data)
def workspace_deteled(self, cloud_id):
def workspace_deleted(self, id, cloud_id):
"""Update back to the VRE API that a workstation has been deleted.
Args:
id (int): The VRE API workstation ID
cloud_id (string): The cloud id that is created with this workstation
"""
data = self._put_data(f'vrw/{id}/status/', data={'status' : 'TERMINATED', 'cloud_id' : cloud_id})
print(data)
@ -479,12 +740,11 @@ if __name__ == "__main__":
user_dn = ldap_client.search_researcher(workspace['researcher_email'])
if user_dn:
# Update user details
_ = ldap_client.update_researcher(user_dn,
workspace['researcher_first_name'],
workspace['researcher_last_name'],
workspace['researcher_email'],
workspace['researcher_mobile_phone'],
workspace['researcher_Pnumber'])
_ = ldap_client.update_researcher(workspace['researcher_first_name'],
workspace['researcher_last_name'],
workspace['researcher_email'],
workspace['researcher_mobile_phone'],
workspace['researcher_Pnumber'])
# Remove existing role and workstation of the research from the research group
ldap_client.remove_researcher_from_group(workspace['study_dn'], user_dn)
@ -514,7 +774,7 @@ if __name__ == "__main__":
logging.info(f'There are {len(left_over_members)} member(s) left in research group: {workspace["study_name"]}')
# Update the VRE API that this workspace is deleted....
vre_api.workspace_deteled(workspace['workspace_id'])
vre_api.workspace_deleted(workspace['workspace_id'], workspace['workspace_dn'])
# Release the lockfile
lockfile.unlink()

33
doc/agent.rst

@ -0,0 +1,33 @@
=====
Agent
=====
----
LDAP
----
.. autoclass:: agent.VRWLDAP
:members:
Exceptions
----------
.. autoexception:: agent.LDAPError
:members:
.. autoexception:: agent.DuplicateResearchGroup
:members:
.. autoexception:: agent.ResearchGroupDoesNotExists
:members:
.. autoexception:: agent.DuplicateResearcher
:members:
.. autoexception:: agent.ResearcherDoesNotExists
:members:
-------
VRE API
-------
.. autoclass:: agent.VRE_API_CLient
:members:

3
doc/conf.py

@ -5,6 +5,9 @@
# https://www.sphinx-doc.org/en/master/usage/configuration.html
# -- Path setup --------------------------------------------------------------
import os
import sys
sys.path.insert(0, os.path.abspath('../'))
# 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

BIN
doc/documentation.pdf

1
doc/index.rst

@ -13,6 +13,7 @@ This document will describe how the VRW agent is working. This will include info
LDAP
VRE
development
agent
------------------
Indices and tables

Loading…
Cancel
Save