diff --git a/plugins/module_utils/proxmox_datacenter_role.py b/plugins/module_utils/proxmox_datacenter_role.py new file mode 100644 index 0000000..2bde0f7 --- /dev/null +++ b/plugins/module_utils/proxmox_datacenter_role.py @@ -0,0 +1,63 @@ +from dataclasses import dataclass, field +from typing import List, Tuple + +from ansible_collections.finallycoffee.proxmox.plugins.module_utils.common import _proxmox_request, ProxmoxAuthInfo + + +@dataclass(frozen=True) +class ProxmoxRole: + name: str + privileges: List = field(default_factory=lambda: []) + built_in: bool = False + + +def get_roles(auth_info: ProxmoxAuthInfo) -> List['ProxmoxRoles']: + role_answer = _proxmox_request('get', '/access/roles', auth_info).json() + return list(map( + lambda r: ProxmoxRole(r['roleid'], r['privs'].split(','), bool(r.get('special', False)) is True), + role_answer['data'] + )) + + +def set_role_data(auth_info: ProxmoxAuthInfo, role: str, privileges: list[str], dry_run: bool, state: str = 'present') -> Tuple[dict, dict]: + existing_role = _proxmox_request('get', f"/access/roles/{role}", auth_info) + existing_role_data = existing_role.json()['data'] + new_role_data = '' + if state == 'present' or state == 'exact': + if existing_role.ok: + # When state is present, we only append all listed privs to existing roles, + # otherwise we set the role's permission list directly + changeset = { + 'privs': ','.join(privileges), + 'append': False if (state == 'exact') else True + } + if not dry_run: + role_res = _proxmox_request('put', f"/access/roles/{role}", auth_info, data=changeset) + role_res.raise_for_status() + else: + # For new roles, state being exact or present is the same thing + if not dry_run: + role_res = _proxmox_request('post', '/access/roles', auth_info, data=({ + 'roleId': role, + 'privs': ','.join(privileges) + })) + role_res.raise_for_status() + if not dry_run: + if role_res.ok: + new_role_data = _proxmox_request('get', f"/access/roles/{role}", auth_info).json()['data'] + else: + new_role_data = {'role': role, 'privs': privileges} + else: + if existing_role.ok: + existing_role_data = existing_role.json()['data'] + if not dry_run: + role_res = _proxmox_request('delete', f"/access/role/{role}", auth_info) + role_res.raise_for_status() + if role_res.ok: + new_role_data = None + else: + new_role_data = None + else: + new_role_data = None + return existing_role_data, new_role_data + diff --git a/plugins/modules/role_info.py b/plugins/modules/role_info.py new file mode 100644 index 0000000..1cd7207 --- /dev/null +++ b/plugins/modules/role_info.py @@ -0,0 +1,127 @@ +#!/usr/bin/python +# coding: utf-8 + +# (c) 2022, Johanna Dorothea Reichmann + +__metaclass__ = type + +import dataclasses +import traceback + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.finallycoffee.proxmox.plugins.module_utils.common import * +from ansible_collections.finallycoffee.proxmox.plugins.module_utils.proxmox_datacenter_role import * + + +DOCUMENTATION = r''' +--- +module: role_info +author: +- Johanna Dorothea Reichmann (transcaffeine@finally.coffee) +requirements: + - python >= 3.9 +short_description: Get configured and builtin roles of a proxmox node +description: + - "Lists all configured roles in a proxmox instance" +options: + proxmox_instance: + description: Location of the proxmox API with scheme, domain name/ip and port, e.g. https://localhost:8006 + type: str + required: true + proxmox_api_token_id: + description: The token ID containing username, realm and token name (format: user@realm!name) + type: str + required: true + proxmox_api_secret: + description: The secret + type: str + required: true + proxmox_api_verify_cert: + description: If the certificate presented for `proxmox_instance_url` should be verified + type: bool + required: false + default: true + role: + description: Role to retrieve information about. If left omitted, return all roles + type: str + required: false +''' + +EXAMPLES = r''' +- name: Retrieve all roles + finallycoffee.proxmox.role_info: + proxmox_instance: https://my.proxmox-node.local:8006 + promox_api_token_id: root@pam!token + proxmox_api_secret: supersecuretokencontent +- name: Retrieve role information for 'PVEAdmin' + finallycoffee.proxmox.role_info: + proxmox_instance: https://my.proxmox-node.local:8006 + promox_api_token_id: root@pam!token + proxmox_api_secret: supersecuretokencontent + role: PVEAdmin +''' + +RETURN = r''' +roles: + description: The retrieved roles + returned: When roles were found (matching the filter) + type: list + elements: dict[str, dict[str], bool] +role: + description: The role's name (`.name`) and privileges (`.privileges`) + returned: When a single role was queried + type: dict[str, list[str]] + +''' + + +def main(): + _ = dict + module = AnsibleModule( + argument_spec=_( + proxmox_instance=_(required=True, type='str'), + proxmox_api_token_id=_(required=True, type='str'), + proxmox_api_secret=_(type='str', required=True, no_log=True), + proxmox_api_verify_cert=_(type='bool', required=False, default=True), + role=_(required=False, type='str'), + ), + supports_check_mode=True + ) + + result = _( + changed=False, + diff={}, + message='' + ) + + roles = [] + try: + roles = get_roles(ProxmoxAuthInfo( + module.params['proxmox_instance'], + module.params['proxmox_api_token_id'], + module.params['proxmox_api_secret'], + module.params['proxmox_api_verify_cert'], + )) + except IOError as owie: + result['msg'] = owie + module.exit_json(**result) + + result['roles'] = list( + map(lambda r: dataclasses.asdict(r), + filter(lambda r: r.name == module.params['role'] + if module.params['role'] is not None + else True, + roles) + ) + ) + if module.params['role'] is not None: + result['role'] = dataclasses.asdict( + list(filter(lambda r: r.name == module.params['role'], roles))[0] + ) + + module.exit_json(**result) + + +if __name__ == '__main__': + main()