diff --git a/plugins/module_utils/role.py b/plugins/module_utils/role.py index 1ebd2feb..70c4459f 100644 --- a/plugins/module_utils/role.py +++ b/plugins/module_utils/role.py @@ -11,7 +11,7 @@ from ..module_utils.utils import PayloadMapper from ..module_utils.rest_client import RestClient -from ..module_utils.typed_classes import TypedDNSConfigToAnsible +from ..module_utils.typed_classes import TypedRoleToAnsible from typing import Union, Any @@ -22,7 +22,7 @@ def __init__(self, uuid: str, name: str): self.name = name @classmethod - def from_ansible(cls, ansible_data: dict[Any, Any]) -> None: + def from_ansible(cls, ansible_data: Any) -> None: pass @classmethod @@ -40,7 +40,7 @@ def from_hypercore( def to_hypercore(self) -> None: pass - def to_ansible(self) -> TypedDNSConfigToAnsible: + def to_ansible(self) -> TypedRoleToAnsible: return dict( uuid=self.uuid, name=self.name, diff --git a/plugins/module_utils/typed_classes.py b/plugins/module_utils/typed_classes.py index dcc90241..f1f89267 100644 --- a/plugins/module_utils/typed_classes.py +++ b/plugins/module_utils/typed_classes.py @@ -48,13 +48,36 @@ class TypedSupportTunnelToAnsible(TypedDict): code: Union[int, None] +# User to ansible return dict. +class TypedUserToAnsible(TypedDict): + uuid: str + username: str + full_name: str + roles: list[TypedRoleToAnsible] + session_limit: int + + +# Role to ansible return dict. +class TypedRoleToAnsible(TypedDict): + uuid: str + name: str + + # Ansible module return Diff dict {before:{} after:{}} class TypedDiff(TypedDict): before: Union[ - dict[Any, Any], TypedRegistrationToAnsible, TypedSupportTunnelToAnsible, None + dict[Any, Any], + TypedRegistrationToAnsible, + TypedSupportTunnelToAnsible, + TypedUserToAnsible, + None, ] after: Union[ - dict[Any, Any], TypedRegistrationToAnsible, TypedSupportTunnelToAnsible, None + dict[Any, Any], + TypedRegistrationToAnsible, + TypedSupportTunnelToAnsible, + TypedUserToAnsible, + None, ] diff --git a/plugins/module_utils/user.py b/plugins/module_utils/user.py index bfcee8f9..4e7821ca 100644 --- a/plugins/module_utils/user.py +++ b/plugins/module_utils/user.py @@ -4,16 +4,26 @@ # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import absolute_import, division, print_function +from __future__ import annotations __metaclass__ = type from ..module_utils.utils import PayloadMapper from ..module_utils.role import Role from ..module_utils.rest_client import RestClient +from ..module_utils.typed_classes import TypedUserToAnsible +from typing import Union class User(PayloadMapper): - def __init__(self, uuid, username, full_name, role_uuids, session_limit): + def __init__( + self, + uuid: str, + username: str, + full_name: str, + role_uuids: list[str], + session_limit: int, + ): self.uuid = uuid self.username = username self.full_name = full_name @@ -25,22 +35,22 @@ def from_ansible(cls): pass @classmethod - def from_hypercore(cls, hypercore_data): - user_dict = hypercore_data - if not user_dict: # In case for get_record, return None if no result is found + def from_hypercore(cls, hypercore_data: dict) -> Union[User, None]: + # In case for get_record, return None if no result is found + if not hypercore_data: return None return cls( - uuid=user_dict["uuid"], - username=user_dict["username"], - full_name=user_dict["fullName"], - role_uuids=user_dict["roleUUIDs"], - session_limit=user_dict["sessionLimit"], + uuid=hypercore_data["uuid"], + username=hypercore_data["username"], + full_name=hypercore_data["fullName"], + role_uuids=hypercore_data["roleUUIDs"], + session_limit=hypercore_data["sessionLimit"], ) def to_hypercore(self): pass - def to_ansible(self, rest_client: RestClient): + def to_ansible(self, rest_client: RestClient) -> TypedUserToAnsible: return dict( uuid=self.uuid, username=self.username, @@ -54,11 +64,13 @@ def to_ansible(self, rest_client: RestClient): session_limit=self.session_limit, ) - def __eq__(self, other): + def __eq__(self, other: object) -> bool: """ One User is equal to another if it has ALL attributes exactly the same. This method is used only in tests. """ + if not isinstance(other, User): + return NotImplemented return all( ( self.uuid == other.uuid, @@ -70,7 +82,9 @@ def __eq__(self, other): ) @classmethod - def get_user_from_uuid(cls, user_uuid, rest_client: RestClient, must_exist=False): + def get_user_from_uuid( + cls, user_uuid, rest_client: RestClient, must_exist: bool = False + ) -> Union[User, None]: hypercore_dict = rest_client.get_record( "/rest/v1/User/{0}".format(user_uuid), must_exist=must_exist ) @@ -79,15 +93,15 @@ def get_user_from_uuid(cls, user_uuid, rest_client: RestClient, must_exist=False @classmethod def get_user_from_username( - cls, username, rest_client: RestClient, must_exist=False - ): + cls, username, rest_client: RestClient, must_exist: bool = False + ) -> Union[User, None]: hypercore_dict = rest_client.get_record( "/rest/v1/User", {"username": username}, must_exist=must_exist ) user = cls.from_hypercore(hypercore_dict) return user - def delete(self, rest_client: RestClient, check_mode=False): + def delete(self, rest_client: RestClient, check_mode: bool = False) -> None: rest_client.delete_record(f"/rest/v1/User/{self.uuid}", check_mode) # returned: # { @@ -95,7 +109,9 @@ def delete(self, rest_client: RestClient, check_mode=False): # "createdUUID": "" # } - def update(self, rest_client: RestClient, payload, check_mode=False): + def update( + self, rest_client: RestClient, payload, check_mode: bool = False + ) -> None: rest_client.update_record(f"/rest/v1/User/{self.uuid}", payload, check_mode) # returned: # { @@ -104,12 +120,12 @@ def update(self, rest_client: RestClient, payload, check_mode=False): # } @classmethod - def create(cls, rest_client: RestClient, payload, check_mode=False): + def create(cls, rest_client: RestClient, payload, check_mode=False) -> User: task_tag = rest_client.create_record("/rest/v1/User", payload, check_mode) user = cls.get_user_from_uuid( task_tag["createdUUID"], rest_client, must_exist=True ) - return user + return user # type: ignore # user is never None # returned # { # "taskTag": "", diff --git a/plugins/modules/user.py b/plugins/modules/user.py index fe451f6d..fed42ec7 100644 --- a/plugins/modules/user.py +++ b/plugins/modules/user.py @@ -17,6 +17,8 @@ short_description: Creates, updates or deletes local hypercore user accounts. description: - Creates, updates or deletes local hypercore user accounts. + - The module in general is NOT idempotent. If C(password) needs to be changed, then module will report `changed=True`, + even if new password value is the same as old password value. version_added: 1.2.0 extends_documentation_fragment: - scale_computing.hypercore.cluster_instance @@ -115,18 +117,22 @@ from ..module_utils.rest_client import CachedRestClient from ..module_utils.user import User from ..module_utils.role import Role +from ..module_utils.typed_classes import TypedUserToAnsible, TypedDiff +from typing import List, Tuple, Union, Dict, Any -def get_role_uuids(module, rest_client: RestClient): +def get_role_uuids(module: AnsibleModule, rest_client: RestClient) -> List[str]: # CachedRestClient is beneficial here since we use for loop and make get requests to the same endpoint many times role_uuids = [] for role_name in module.params["roles"]: role = Role.get_role_from_name(role_name, rest_client, must_exist=True) - role_uuids.append(role.uuid) + role_uuids.append(role.uuid) # type: ignore # since must_exist=True, role is never None return role_uuids -def data_for_create_user(module, rest_client: RestClient): +def data_for_create_user( + module: AnsibleModule, rest_client: RestClient +) -> Dict[Any, Any]: data = {} data["username"] = module.params[ "username" @@ -142,7 +148,9 @@ def data_for_create_user(module, rest_client: RestClient): return data -def create_user(module, rest_client: RestClient): +def create_user( + module: AnsibleModule, rest_client: RestClient +) -> Tuple[bool, TypedUserToAnsible, TypedDiff]: data = data_for_create_user(module, rest_client) user = User.create(rest_client, data).to_ansible(rest_client) return ( @@ -152,7 +160,9 @@ def create_user(module, rest_client: RestClient): ) -def data_for_update_user(module, rest_client: RestClient, user): +def data_for_update_user( + module: AnsibleModule, rest_client: RestClient, user: User +) -> Dict[Any, Any]: data = {} if module.params["username_new"]: if user.username != module.params["username_new"]: @@ -174,35 +184,40 @@ def data_for_update_user(module, rest_client: RestClient, user): return data -def update_user(module, rest_client: RestClient, user): +def update_user( + module: AnsibleModule, rest_client: RestClient, user: User +) -> Tuple[bool, TypedUserToAnsible, TypedDiff]: data = data_for_update_user(module, rest_client, user) if data: user.update(rest_client, data) - user_after = User.get_user_from_uuid( - user.uuid, rest_client, must_exist=True - ).to_ansible(rest_client) - user = user.to_ansible(rest_client) + user_after = User.get_user_from_uuid(user.uuid, rest_client, must_exist=True) + user_after_to_ansible = user_after.to_ansible(rest_client) # type: ignore # user_after is never None + user_to_ansible = user.to_ansible(rest_client) return ( True, - user_after, - dict(before=user, after=user_after), + user_after_to_ansible, + dict(before=user_to_ansible, after=user_after_to_ansible), ) - user = user.to_ansible(rest_client) + user_to_ansible = user.to_ansible(rest_client) return ( False, - user, - dict(before=user, after=user), + user_to_ansible, + dict(before=user_to_ansible, after=user_to_ansible), ) -def delete_user(rest_client: RestClient, user): +def delete_user( + rest_client: RestClient, user: Union[User, None] +) -> Tuple[bool, Union[TypedUserToAnsible, Dict[None, None]], TypedDiff]: if not user: - return False, dict(), dict(before={}, after={}) + return (False, dict(), dict(before={}, after={})) user.delete(rest_client) - return True, dict(), dict(before=user.to_ansible(rest_client), after={}) + return (True, dict(), dict(before=user.to_ansible(rest_client), after={})) -def run(module, rest_client: RestClient): +def run( + module: AnsibleModule, rest_client: RestClient +) -> Tuple[bool, Union[TypedUserToAnsible, Dict[None, None]], TypedDiff]: user = User.get_user_from_username( module.params["username"], rest_client, must_exist=False ) @@ -211,11 +226,11 @@ def run(module, rest_client: RestClient): return update_user(module, rest_client, user) else: return create_user(module, rest_client) - if module.params["state"] == "absent": + else: return delete_user(rest_client, user) -def main(): +def main() -> None: module = AnsibleModule( supports_check_mode=False, argument_spec=dict( @@ -249,7 +264,7 @@ def main(): try: client = Client.get_client(module.params["cluster_instance"]) - rest_client = CachedRestClient(client) + rest_client = CachedRestClient(client) # type: ignore changed, record, diff = run(module, rest_client) module.exit_json(changed=changed, record=record, diff=diff) except errors.ScaleComputingError as e: diff --git a/plugins/modules/user_info.py b/plugins/modules/user_info.py index 4dc3193a..3c7aa6fb 100644 --- a/plugins/modules/user_info.py +++ b/plugins/modules/user_info.py @@ -66,19 +66,25 @@ from ..module_utils.client import Client from ..module_utils.user import User from ..module_utils.utils import get_query +from ..module_utils.typed_classes import TypedUserToAnsible +from typing import List, Union -def run(module, rest_client): +def run( + module: AnsibleModule, rest_client: RestClient +) -> List[Union[TypedUserToAnsible, None]]: query = get_query( module.params, "username", ansible_hypercore_map=dict(username="username") ) return [ - User.from_hypercore(hypercore_data=hypercore_dict).to_ansible(rest_client) + User.from_hypercore(hypercore_data=hypercore_dict).to_ansible( # type: ignore + rest_client + ) for hypercore_dict in rest_client.list_records("/rest/v1/User", query) ] -def main(): +def main() -> None: module = AnsibleModule( supports_check_mode=True, argument_spec=dict( diff --git a/pyproject.toml b/pyproject.toml index 794c5f89..7b60ca3c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,8 +50,6 @@ module = [ "plugins.modules.snapshot_schedule", "plugins.modules.snapshot_schedule_info", "plugins.modules.task_wait", - "plugins.modules.user_info", - "plugins.modules.user", "plugins.modules.vm_boot_devices", "plugins.modules.vm_clone", "plugins.modules.vm_disk", diff --git a/tests/integration/targets/support_tunnel/tasks/main.yml b/tests/integration/targets/support_tunnel/tasks/main.yml index 1b2ac69f..9802d61c 100644 --- a/tests/integration/targets/support_tunnel/tasks/main.yml +++ b/tests/integration/targets/support_tunnel/tasks/main.yml @@ -5,7 +5,7 @@ SC_PASSWORD: "{{ sc_password }}" SC_TIMEOUT: "{{ sc_timeout }}" vars: - code: "4422" + code: 4422 block: - name: Cleanup @@ -26,11 +26,11 @@ that: - support_tunnel is changed - support_tunnel.record.open == true - - support_tunnel.record.code == "{{ code }}" + - support_tunnel.record.code == {{ code }} - support_tunnel.diff.before.open == false - not support_tunnel.diff.before.code - support_tunnel.diff.after.open == true - - support_tunnel.diff.after.code == "{{ code }}" + - support_tunnel.diff.after.code == {{ code }} - name: Open support tunnel - check with info module scale_computing.hypercore.support_tunnel_info: @@ -38,7 +38,7 @@ - ansible.builtin.assert: that: - support_tunnel.record.open == true - - support_tunnel.record.code == "{{ code }}" + - support_tunnel.record.code == {{ code }} - name: Open support tunnel - idempotence scale_computing.hypercore.support_tunnel: @@ -49,11 +49,11 @@ that: - support_tunnel is not changed - support_tunnel.record.open == true - - support_tunnel.record.code == "{{ code }}" + - support_tunnel.record.code == {{ code }} - support_tunnel.diff.before.open == true - - support_tunnel.diff.before.code == "{{ code }}" + - support_tunnel.diff.before.code == {{ code }} - support_tunnel.diff.after.open == true - - support_tunnel.diff.after.code == "{{ code }}" + - support_tunnel.diff.after.code == {{ code }} - name: Open support tunnel - check with info module scale_computing.hypercore.support_tunnel_info: @@ -61,7 +61,7 @@ - ansible.builtin.assert: that: - support_tunnel.record.open == true - - support_tunnel.record.code == "{{ code }}" + - support_tunnel.record.code == {{ code }} - name: Close support tunnel scale_computing.hypercore.support_tunnel: @@ -73,7 +73,7 @@ - support_tunnel.record.open == false - not support_tunnel.record.code - support_tunnel.diff.before.open == true - - support_tunnel.diff.before.code == "{{ code }}" + - support_tunnel.diff.before.code == {{ code }} - support_tunnel.diff.after.open == false - not support_tunnel.diff.after.code