#!/usr/bin/python
# -*- coding: utf-8 -*-

# Copyright (c) 2022, Håkon Lerring
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later

from __future__ import absolute_import, division, print_function

__metaclass__ = type

DOCUMENTATION = """
module: consul_role
short_description: Manipulate Consul roles
version_added: 7.5.0
description:
 - Allows the addition, modification and deletion of roles in a consul
   cluster via the agent. For more details on using and configuring ACLs,
   see U(https://www.consul.io/docs/guides/acl.html).
author:
  - Håkon Lerring (@Hakon)
extends_documentation_fragment:
  - community.general.consul
  - community.general.consul.token
  - community.general.attributes
attributes:
  check_mode:
    support: full
  diff_mode:
    support: partial
    details:
      - In check mode the diff will miss operational attributes.
    version_added: 8.3.0
options:
  name:
    description:
      - A name used to identify the role.
    required: true
    type: str
  state:
    description:
      - whether the role should be present or absent.
    choices: ['present', 'absent']
    default: present
    type: str
  description:
    description:
      - Description of the role.
      - If not specified, the assigned description will not be changed.
    type: str
  policies:
    type: list
    elements: dict
    description:
      - List of policies to attach to the role. Each policy is a dict.
      - If the parameter is left blank, any policies currently assigned will not be changed.
      - Any empty array (V([])) will clear any policies previously set.
    suboptions:
      name:
        description:
          - The name of the policy to attach to this role; see M(community.general.consul_policy) for more info.
          - Either this or O(policies[].id) must be specified.
        type: str
      id:
        description:
          - The ID of the policy to attach to this role; see M(community.general.consul_policy) for more info.
          - Either this or O(policies[].name) must be specified.
        type: str
  templated_policies:
    description:
      - The list of templated policies that should be applied to the role.
    type: list
    elements: dict
    version_added: 8.3.0
    suboptions:
      template_name:
        description:
          - The templated policy name.
        type: str
        required: true
      template_variables:
        description:
          - The templated policy variables.
          - Not all templated policies require variables.
        type: dict
  service_identities:
    type: list
    elements: dict
    description:
      - List of service identities to attach to the role.
      - If not specified, any service identities currently assigned will not be changed.
      - If the parameter is an empty array (V([])), any node identities assigned will be unassigned.
    suboptions:
      service_name:
        description:
          - The name of the node.
          - Must not be longer than 256 characters, must start and end with a lowercase alphanumeric character.
          - May only contain lowercase alphanumeric characters as well as - and _.
          - This suboption has been renamed from O(service_identities[].name) to O(service_identities[].service_name)
            in community.general 8.3.0. The old name can still be used.
        type: str
        required: true
        aliases:
          - name
      datacenters:
        description:
          - The datacenters the policies will be effective.
          - This will result in effective policy only being valid in this datacenter.
          - If an empty array (V([])) is specified, the policies will valid in all datacenters.
          - including those which do not yet exist but may in the future.
        type: list
        elements: str
  node_identities:
    type: list
    elements: dict
    description:
      - List of node identities to attach to the role.
      - If not specified, any node identities currently assigned will not be changed.
      - If the parameter is an empty array (V([])), any node identities assigned will be unassigned.
    suboptions:
      node_name:
        description:
          - The name of the node.
          - Must not be longer than 256 characters, must start and end with a lowercase alphanumeric character.
          - May only contain lowercase alphanumeric characters as well as - and _.
          - This suboption has been renamed from O(node_identities[].name) to O(node_identities[].node_name)
            in community.general 8.3.0. The old name can still be used.
        type: str
        required: true
        aliases:
          - name
      datacenter:
        description:
          - The nodes datacenter.
          - This will result in effective policy only being valid in this datacenter.
        type: str
        required: true
"""

EXAMPLES = """
- name: Create a role with 2 policies
  community.general.consul_role:
    host: consul1.example.com
    token: some_management_acl
    name: foo-role
    policies:
      - id: 783beef3-783f-f41f-7422-7087dc272765
      - name: "policy-1"

- name: Create a role with service identity
  community.general.consul_role:
    host: consul1.example.com
    token: some_management_acl
    name: foo-role-2
    service_identities:
      - name: web
        datacenters:
          - dc1

- name: Create a role with node identity
  community.general.consul_role:
    host: consul1.example.com
    token: some_management_acl
    name: foo-role-3
    node_identities:
      - name: node-1
        datacenter: dc2

- name: Remove a role
  community.general.consul_role:
    host: consul1.example.com
    token: some_management_acl
    name: foo-role-3
    state: absent
"""

RETURN = """
role:
    description: The role object.
    returned: success
    type: dict
    sample:
        {
            "CreateIndex": 39,
            "Description": "",
            "Hash": "Trt0QJtxVEfvTTIcdTUbIJRr6Dsi6E4EcwSFxx9tCYM=",
            "ID": "9a300b8d-48db-b720-8544-a37c0f5dafb5",
            "ModifyIndex": 39,
            "Name": "foo-role",
            "Policies": [
                {"ID": "b1a00172-d7a1-0e66-a12e-7a4045c4b774", "Name": "foo-access"}
            ]
        }
operation:
    description: The operation performed on the role.
    returned: changed
    type: str
    sample: update
"""

from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.consul import (
    AUTH_ARGUMENTS_SPEC,
    OPERATION_READ,
    _ConsulModule,
)


class ConsulRoleModule(_ConsulModule):
    api_endpoint = "acl/role"
    result_key = "role"
    unique_identifier = "id"

    def endpoint_url(self, operation, identifier=None):
        if operation == OPERATION_READ:
            return [self.api_endpoint, "name", self.params["name"]]
        return super(ConsulRoleModule, self).endpoint_url(operation, identifier)


NAME_ID_SPEC = dict(
    name=dict(type="str"),
    id=dict(type="str"),
)

NODE_ID_SPEC = dict(
    node_name=dict(type="str", required=True, aliases=["name"]),
    datacenter=dict(type="str", required=True),
)

SERVICE_ID_SPEC = dict(
    service_name=dict(type="str", required=True, aliases=["name"]),
    datacenters=dict(type="list", elements="str"),
)

TEMPLATE_POLICY_SPEC = dict(
    template_name=dict(type="str", required=True),
    template_variables=dict(type="dict"),
)

_ARGUMENT_SPEC = {
    "name": dict(type="str", required=True),
    "description": dict(type="str"),
    "policies": dict(
        type="list",
        elements="dict",
        options=NAME_ID_SPEC,
        mutually_exclusive=[("name", "id")],
        required_one_of=[("name", "id")],
    ),
    "templated_policies": dict(
        type="list",
        elements="dict",
        options=TEMPLATE_POLICY_SPEC,
    ),
    "node_identities": dict(
        type="list",
        elements="dict",
        options=NODE_ID_SPEC,
    ),
    "service_identities": dict(
        type="list",
        elements="dict",
        options=SERVICE_ID_SPEC,
    ),
    "state": dict(default="present", choices=["present", "absent"]),
}
_ARGUMENT_SPEC.update(AUTH_ARGUMENTS_SPEC)


def main():
    module = AnsibleModule(
        _ARGUMENT_SPEC,
        supports_check_mode=True,
    )
    consul_module = ConsulRoleModule(module)
    consul_module.execute()


if __name__ == "__main__":
    main()