#!/usr/bin/python # Copyright (c) 2023, Pedro Nascimento # GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) # SPDX-License-Identifier: GPL-3.0-or-later from __future__ import annotations DOCUMENTATION = r""" module: nomad_token author: Pedro Nascimento (@apecnascimento) version_added: "8.1.0" short_description: Manage Nomad ACL tokens description: - This module allows to create Bootstrap tokens, create ACL tokens, update ACL tokens, and delete ACL tokens. requirements: - python-nomad extends_documentation_fragment: - community.general.nomad - community.general.attributes attributes: check_mode: support: none diff_mode: support: none options: name: description: - Name of ACL token to create. type: str token_type: description: - The type of the token can be V(client), V(management), or V(bootstrap). choices: ["client", "management", "bootstrap"] type: str default: "client" policies: description: - A list of the policies assigned to the token. type: list elements: str default: [] global_replicated: description: - Indicates whether or not the token was created with the C(--global). type: bool default: false state: description: - Create or remove ACL token. choices: ["present", "absent"] required: true type: str seealso: - name: Nomad ACL documentation description: Complete documentation for Nomad API ACL. link: https://developer.hashicorp.com/nomad/api-docs/acl/tokens """ EXAMPLES = r""" - name: Create boostrap token community.general.nomad_token: host: localhost token_type: bootstrap state: present - name: Create ACL token community.general.nomad_token: host: localhost name: "Dev token" token_type: client policies: - readonly global_replicated: false state: absent - name: Update ACL token Dev token community.general.nomad_token: host: localhost name: "Dev token" token_type: client policies: - readonly - devpolicy global_replicated: false state: absent - name: Delete ACL token community.general.nomad_token: host: localhost name: "Dev token" state: absent """ RETURN = r""" result: description: Result returned by nomad. returned: always type: dict sample: { "accessor_id": "0d01c55f-8d63-f832-04ff-1866d4eb594e", "create_index": 14, "create_time": "2023-11-12T18:48:34.248857001Z", "expiration_time": null, "expiration_ttl": "", "global": true, "hash": "eSn8H8RVqh8As8WQNnC2vlBRqXy6DECogc5umzX0P30=", "modify_index": 836, "name": "devs", "policies": [ "readonly" ], "roles": null, "secret_id": "12e878ab-e1f6-e103-b4c4-3b5173bb4cea", "type": "client" } """ from ansible.module_utils.basic import AnsibleModule, missing_required_lib from ansible.module_utils.common.text.converters import to_native import_nomad = None try: import nomad import_nomad = True except ImportError: import_nomad = False def get_token(name, nomad_client): tokens = nomad_client.acl.get_tokens() token = next((token for token in tokens if token.get("Name") == name), None) return token def transform_response(nomad_response): transformed_response = { "accessor_id": nomad_response["AccessorID"], "create_index": nomad_response["CreateIndex"], "create_time": nomad_response["CreateTime"], "expiration_ttl": nomad_response["ExpirationTTL"], "expiration_time": nomad_response["ExpirationTime"], "global": nomad_response["Global"], "hash": nomad_response["Hash"], "modify_index": nomad_response["ModifyIndex"], "name": nomad_response["Name"], "policies": nomad_response["Policies"], "roles": nomad_response["Roles"], "secret_id": nomad_response["SecretID"], "type": nomad_response["Type"], } return transformed_response argument_spec = dict( host=dict(required=True, type="str"), port=dict(type="int", default=4646), state=dict(required=True, choices=["present", "absent"]), use_ssl=dict(type="bool", default=True), timeout=dict(type="int", default=5), validate_certs=dict(type="bool", default=True), client_cert=dict(type="path"), client_key=dict(type="path"), namespace=dict(type="str"), token=dict(type="str", no_log=True), name=dict(type="str"), token_type=dict(choices=["client", "management", "bootstrap"], default="client"), policies=dict(type="list", elements="str", default=[]), global_replicated=dict(type="bool", default=False), ) def setup_module_object(): module = AnsibleModule( argument_spec=argument_spec, supports_check_mode=False, required_one_of=[["name", "token_type"]], required_if=[ ("token_type", "client", ("name",)), ("token_type", "management", ("name",)), ], ) return module def setup_nomad_client(module): if not import_nomad: module.fail_json(msg=missing_required_lib("python-nomad")) certificate_ssl = (module.params.get("client_cert"), module.params.get("client_key")) nomad_client = nomad.Nomad( host=module.params.get("host"), port=module.params.get("port"), secure=module.params.get("use_ssl"), timeout=module.params.get("timeout"), verify=module.params.get("validate_certs"), cert=certificate_ssl, namespace=module.params.get("namespace"), token=module.params.get("token"), ) return nomad_client def run(module): nomad_client = setup_nomad_client(module) msg = "" result = {} changed = False if module.params.get("state") == "present": if module.params.get("token_type") == "bootstrap": try: current_token = get_token("Bootstrap Token", nomad_client) if current_token: msg = "ACL bootstrap already exist." else: nomad_result = nomad_client.acl.generate_bootstrap() msg = "Boostrap token created." result = transform_response(nomad_result) changed = True except nomad.api.exceptions.URLNotAuthorizedNomadException: try: nomad_result = nomad_client.acl.generate_bootstrap() msg = "Boostrap token created." result = transform_response(nomad_result) changed = True except Exception as e: module.fail_json(msg=to_native(e)) else: try: token_info = { "Name": module.params.get("name"), "Type": module.params.get("token_type"), "Policies": module.params.get("policies"), "Global": module.params.get("global_replicated"), } current_token = get_token(token_info["Name"], nomad_client) if current_token: token_info["AccessorID"] = current_token["AccessorID"] nomad_result = nomad_client.acl.update_token(current_token["AccessorID"], token_info) msg = "ACL token updated." result = transform_response(nomad_result) changed = True else: nomad_result = nomad_client.acl.create_token(token_info) msg = "ACL token Created." result = transform_response(nomad_result) changed = True except Exception as e: module.fail_json(msg=to_native(e)) if module.params.get("state") == "absent": if not module.params.get("name"): module.fail_json(msg="name is needed to delete token.") if module.params.get("token_type") == "bootstrap" or module.params.get("name") == "Bootstrap Token": module.fail_json(msg="Delete ACL bootstrap token is not allowed.") try: token = get_token(module.params.get("name"), nomad_client) if token: nomad_client.acl.delete_token(token.get("AccessorID")) msg = "ACL token deleted." changed = True else: msg = f"No token with name '{module.params['name']}' found" except Exception as e: module.fail_json(msg=to_native(e)) module.exit_json(changed=changed, msg=msg, result=result) def main(): module = setup_module_object() run(module) if __name__ == "__main__": main()