From a999b6304470d4e10720dc4831a40e6a688fb12c Mon Sep 17 00:00:00 2001 From: Johanna Dorothea Reichmann Date: Sat, 11 Jun 2022 23:04:43 +0200 Subject: [PATCH] feat(realm_info): add plugin for retrieving realms --- plugins/module_utils/proxmox_api.py | 76 +++++++++++++++++ plugins/modules/realm_info.py | 124 ++++++++++++++++++++++++++++ 2 files changed, 200 insertions(+) create mode 100644 plugins/module_utils/proxmox_api.py create mode 100644 plugins/modules/realm_info.py diff --git a/plugins/module_utils/proxmox_api.py b/plugins/module_utils/proxmox_api.py new file mode 100644 index 0000000..089e867 --- /dev/null +++ b/plugins/module_utils/proxmox_api.py @@ -0,0 +1,76 @@ +from dataclasses import dataclass +from typing import List, Tuple + +import requests + +API_BASE_PATH: str = "/api2/json" + + +@dataclass() +class ProxmoxAuthInfo: + instance_url: str + token_id: str + secret: str + verify_cert: bool = True + + +@dataclass(frozen=True) +class ProxmoxRealm: + type: str + realm: str + comment: str = None + default: bool = False + + +def get_realms(auth_info: ProxmoxAuthInfo) -> List['ProxmoxRealm']: + realm_answer = _proxmox_request('get', '/access/domains', auth_info).json() + return list(map( + lambda r: ProxmoxRealm(r['type'], r['realm'], r.get('comment', None), r.get('default', False)), + realm_answer['data'] + )) + + +def set_realm_data(auth_info: ProxmoxAuthInfo, realm: str, config: dict[str, str], dry_run: bool, state: str = 'present') -> Tuple[dict, dict]: + existing_realm = _proxmox_request('get', f"/access/domains/{realm}", auth_info) + existing_realm_data = None + new_realm_data = {} + if state == 'present': + if existing_realm.ok: + existing_realm_data = existing_realm.json()['data'] + if not dry_run: + realm_res = _proxmox_request('put', f"/access/domains/{realm}", auth_info, data=config) + realm_res.raise_for_status() + else: + if not dry_run: + realm_res = _proxmox_request('post', '/access/domains', auth_info, data=(config | {'realm': realm})) + realm_res.raise_for_status() + if not dry_run: + if realm_res.ok: + new_realm_data = _proxmox_request('get', f"/access/domains/{realm}", auth_info).json()['data'] + else: + new_realm_data = config | {'name': realm} + else: + if existing_realm.ok: + existing_realm_data = existing_realm.json()['data'] + if not dry_run: + realm_res = _proxmox_request('delete', f"/access/domains/{realm}", auth_info) + realm_res.raise_for_status() + if realm_res.ok: + new_realm_data = None + else: + new_realm_data = None + else: + new_realm_data = None + return existing_realm_data, new_realm_data + + +def _get_token_from_auth_info(auth_info: ProxmoxAuthInfo) -> str: + return f"PVEAPIToken={auth_info.token_id}={auth_info.secret}" + + +def _proxmox_request(method: str, endpoint: str, auth_info: ProxmoxAuthInfo, **params): + return requests.request(method, + f"{auth_info.instance_url}{API_BASE_PATH}{endpoint}", + headers={'Authorization': _get_token_from_auth_info(auth_info)}, + verify=auth_info.verify_cert, + **params) diff --git a/plugins/modules/realm_info.py b/plugins/modules/realm_info.py new file mode 100644 index 0000000..e426208 --- /dev/null +++ b/plugins/modules/realm_info.py @@ -0,0 +1,124 @@ +#!/usr/bin/python +# coding: utf-8 + +# (c) 2022, Johanna Dorothea Reichmann + +__metaclass__ = type + +import dataclasses +import traceback + +from ansible.module_utils.basic import AnsibleModule + +LIB_IMP_ERR = None +try: + from ansible_collections.finallycoffee.proxmox.plugins.module_utils.proxmox_api import * + HAS_LIB = True +except: + HAS_LIB = False + LIB_IMP_ERR = traceback.format_exc() + + +DOCUMENTATION = r''' +--- +module: realm_info +author: +- Johanna Dorothea Reichmann (transcaffeine@finally.coffee) +requirements: + - python >= 3.9 +short_description: Get configured realms of a proxmox node +description: + - "Lists all configured realms 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 + realm: + description: Realm to retrieve information about. If left omitted, return all realms + type: str + required: false +''' + +EXAMPLES = r''' +- name: Retrieve all reams + finallycoffee.proxmox.realm_info: + proxmox_instance: https://my.proxmox-node.local:8006 + promox_api_token_id: root@pam!token + proxmox_api_secret: supersecuretokencontent +- name: Retrieve realm information for 'myrealm' + finallycoffee.proxmox.realm_info: + proxmox_instance: https://my.proxmox-node.local:8006 + promox_api_token_id: root@pam!token + proxmox_api_secret: supersecuretokencontent + realm: myrealm +''' + +RETURN = r''' +realms: + description: The retrieved realms + returned: When realms were found (matching the filter) + type: list + elements: dict[str, str, str, 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), + realm=_(required=False, type='str'), + ), + supports_check_mode=True + ) + + result = _( + changed=False, + diff={}, + message='' + ) + + realms = [] + try: + realms = get_realms(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['realms'] = list( + map(lambda r: dataclasses.asdict(r), + filter(lambda r: r.realm == module.params['realm'] + if module.params['realm'] is not None + else True, + realms) + ) + ) + + module.exit_json(**result) + + +if __name__ == '__main__': + main()