#!/usr/bin/python
# Copyright (c) 2017-present Alibaba Group Holding Limited. <xiaozhu36>
# 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)

__metaclass__ = type

ANSIBLE_METADATA = {'metadata_version': '1.1',
                    'status': ['preview'],
                    'supported_by': 'community'}

DOCUMENTATION = '''
---
module: ali_rds_instance
short_description: Create, Restart or Delete an RDS Instance in Alibaba Cloud.
description:
    - Create, Restart, Delete and Modify connection_string, spec for RDS Instance.
    - An unique ali_rds_instance module is determined by parameters db_instance_name.
options:
  state:
    description:
      - If I(state=present), instance will be created.
      - If I(state=present) and instance exists, it will be updated.
      - If I(state=absent), instance will be removed.
      - If I(state=restart), instance will be restarted.
    choices: ['present', 'restart', 'absent']
    default: 'present'
    type: str
  zone_id:
    description:
      - Aliyun availability zone ID in which to launch the instance.
        If it is not specified, it will be allocated by system automatically.
    aliases: ['alicloud_zone']
    type: str
  engine:
    description:
      - The engine type of the database. Required when C(state=present).
    choices: ['MySQL', 'SQLServer', 'PostgreSQL', 'PPAS', 'MariaDB']
    type: str
  engine_version:
    description:
      - The version of the database. Required when C(state=present).
      - MySQL (5.5 | 5.6 | 5.7 | 8.0).
      - SQL Server (2008r2 | 2012 | 2012_ent_ha | 2012_std_ha | 2012_web | 2016_ent_ha | 2016_std_ha | 2016_web | 2017_ent).
      - PostgreSQL (9.4 | 10.0).
      - PPAS (9.3 | 10.0).
      - MariaDB (10.3).
      - see more (https://www.alibabacloud.com/help/doc-detail/26228.htm).
    type: str
  db_instance_class:
    description:
      - The instance type (specifications). For more information, see(https://www.alibabacloud.com/help/doc-detail/26312.htm).
        Required when C(state=present).
    aliases: ['instance_class']
    type: str
  db_instance_storage:
    description:
      - The storage capacity of the instance. Unit(GB). This value must be a multiple of 5. For more information see(https://www.alibabacloud.com/help/doc-detail/26312.htm).
        Required when C(state=present).
    aliases: ['instance_storage']
    type: int
  db_instance_net_type:
    description:
      - Instance of the network connection type (Internet on behalf of the public network, Intranet on behalf of the private network）
        Required when C(state=present).
    aliases: ['instance_net_type']
    choices: ["Internet", "Intranet"]
    type: str
  db_instance_name:
    description:
      - The instance name. It starts with a letter and contains 2 to 255 characters, including letters, digits, underscores (_), and hyphens (-).
        It cannot start with http:// or https://.
      - One of I(db_instance_id) and I(db_instance_name) must be specified when operate existing instance.
    aliases: ['description', 'name']
    type: str
  db_instance_id:
    description:
      - The instance id.
      - One of I(db_instance_id) and I(db_instance_name) must be specified when operate existing instance.
    aliases: ['id']
    type: str
  security_ip_list:
    description:
      - The IP address whitelist of the instance. Separate multiple IP addresses with commas (,). It can include up to 1,000 IP addresses. The IP addresses support two formats.
        IP address format. For example, 10.23.12.24. Classless Inter-Domain Routing (CIDR) format. For example, 10.23.12.24/24 (where /24 indicates the number of bits for the prefix of the IP address, in the range of 1 to 32).
        Required when C(state=present).
    aliases: ['security_ips']
    type: str
  pay_type:
    description:
      - The billing method of the instance. Required when C(state=present).
    choices: ["PostPaid", "PrePaid"]
    type: str
  period:
    description:
      - The duration of the instance. Required when C(pay_type=PrePaid).
    choices: [1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 24, 36]
    default: 1
    type: int
  connection_mode:
    description:
      - The access mode of the instance.
    choices: ["Standard", "Safe"]
    type: str
  vswitch_id:
    description:
      - The ID of the VSwitch.
        Required when C(engine=MariaDB)
    type: str
  private_ip_address:
    description:
      - The intranet IP address of the instance. It must be within the IP address range provided by the switch. 
        By default, the system automatically assigns an IP address based on the VPCId and VSwitchId.
    type: str
  auto_renew:
    description:
      - Indicates whether the instance is automatically renewed
    type: bool
    default: False
    aliases: ['auto_renew']
  port:
    description:
      - The target port.
    type: str
  auto_pay:
    description:
      - Auto renew or not.
    type: bool
    default: False
  current_connection_string:
    description:
      - The current connection address of an instance. It can be an internal network address, a public network address, or a classic network address in the hybrid access mode.
    type: str
  connection_string_prefix:
    description:
      - The prefix of the target connection address. Only the prefix of CurrentConnectionString can be modified.
    type: str
  tags:
    description:
      - A hash/dictionaries of rds tags. C({"key":"value"})
    type: dict
  purge_tags:
    description:
      - Delete existing tags on the rds that are not specified in the task.
        If True, it means you have to specify all the desired tags on each task affecting a rds.
    default: False
    type: bool
author:
    - "He Guimin (@xiaozhu36)"
    - "Li Xue (@lixue323)"
requirements:
    - "python >= 3.6"
    - "footmark >= 1.16.0"
extends_documentation_fragment:
    - alibaba.alicloud.alicloud
'''

EXAMPLES = '''
- name: Changed. Add Tags.
  alibaba.alicloud.ali_rds_instance:
    db_instance_description: '{{ name }}'
    tags:
      TAG: "add1"
      TAG2: "add2"

- name: Changed. Removing tags.
  alibaba.alicloud.ali_rds_instance:
    db_instance_description: '{{ name }}'
    purge_tags: True
    tags:
      TAG: "add1"

- name: Changed. allocate instance public connection string
  alibaba.alicloud.ali_rds_instance:
    db_instance_description: '{{ name }}'
    connection_string_prefix: publicave-89asd
    port: 3165

- name: release instance public connection string
  alibaba.alicloud.ali_rds_instance:
    state: absent
    db_instance_description: '{{ name }}'
    current_connection_string: publicave-89asd.mysql.rds.aliyuncs.com

- name: restart rds instance
  alibaba.alicloud.ali_rds_instance:
    db_instance_description: '{{ name }}'
    state: restart

- name: Changed. modify instance spec
  alibaba.alicloud.ali_rds_instance:
    db_instance_description: '{{ name }}'
    db_instance_class: rds.mysql.c2.xlarge
    db_instance_storage: 40

- name: Changed. modify instance current connection string
  alibaba.alicloud.ali_rds_instance:
    current_connection_string: '{{ rds.instances.0.id }}.mysql.rds.aliyuncs.com'
    db_instance_description: '{{ name }}'
    connection_string_prefix: private-ansible
    port: 3307

- name: Changed. Deleting rds
  alibaba.alicloud.ali_rds_instance:
    state: absent
    db_instance_description: '{{ name }}'
'''

RETURN = '''
instances:
    description: Describe the info after operating rds instance.
    returned: always
    type: complex
    contains:
        db_instance_class:
            description: The type of the instance.
            returned: always
            type: str
            sample: rds.mysql.t1.small
        db_instance_description:
            description: The description of the instance.
            returned: always
            type: str
            sample: ansible_test_rds
        db_instance_id:
            description: The ID of the instance.
            returned: always
            type: str
            sample: rm-uf6wjk5xxxxxxxxxx
        db_instance_net_type:
            description: The network type of the instance.
            returned: always
            type: str
            sample: Internet
        db_instance_status:
            description: The status of the instance.
            returned: always
            type: str
            sample: Running
        db_instance_type:
            description: The type of the instance role.
            returned: always
            type: str
            sample: Primary
        engine:
            description: The type of the database.
            returned: always
            type: str
            sample: MySQL
        engine_version:
            description: The version of the database.
            returned: always
            type: str
            sample: 5.6
        id:
            description: alias of 'db_instance_id'.
            returned: always
            type: str
            sample: rm-uf6wjk5xxxxxxxxxx
        type:
            description: alias of 'db_instance_type'.
            returned: always
            type: str
            sample: Primary
        instance_network_type:
            description: The network type of the instance.
            returned: always
            type: str
            sample: VPC
        name:
            description: alias of 'db_instance_description'.
            returned: always
            type: str
            sample: ansible_test_rds
        pay_type:
            description: The billing method of the instance.
            returned: always
            type: str
            sample: Postpaid
        resource_group_id:
            description: The ID of the resource group.
            returned: always
            type: str
            sample: rg-acfmyxxxxxxx
        status:
            description: alias of 'db_instance_status'
            returned: always
            type: str
            sample: Running
        vpc_cloud_instance_id:
            description: The ID of the VPC instance
            returned: always
            type: str
            sample: rm-uf6wjk5xxxxxxx
        vpc_id:
            description: The ID of the VPC.
            returned: always
            type: str
            sample: vpc-uf6f7l4fg90xxxxxxx
        vswitch_id:
            description: The ID of the VSwitch.
            returned: always
            type: str
            sample: vsw-uf6adz52c2pxxxxxxx
        lock_mode:
            description: The lock mode of the instance.
            returned: always
            type: str
            sample: Unlock
        connection_mode:
            description: The access mode of the instance.
            returned: always
            type: str
            sample: Standard
        account_max_quantity:
            description: The maximum number of accounts that can be created in an instance.
            returned: always
            type: int
            sample: 50
        account_type:
            description: The type of the account.
            returned: always
            type: str
            sample: Mix
        auto_upgrade_minor_version:
            description: The method of upgrading an instance to a minor version.
            returned: always
            type: str
            sample: Auto
        availability_value:
            description: The availability of the instance.
            returned: always
            type: str
            sample: 100.0%
        category:
            description: The edition (series) of the instance.
            returned: always
            type: str
            sample: Basic
        connection_string:
            description: The private IP address of the instance.
            returned: always
            type: str
            sample: rm-uf6wjk5xxxxxxxxxx.mysql.rds.aliyuncs.com
        creation_time:
            description: The time when the instance is created
            returned: always
            type: str
            sample: '2011-05-30T12:11:04Z'
        current_kernel_version:
            description: The current kernel version.
            returned: always
            type: str
            sample: rds_20181010
        db_instance_class_type:
            description: The instance type (specifications).
            returned: always
            type: str
            sample: rds.mys2.small
        db_instance_cpu:
            description: The count of the instance cpu.
            returned: always
            type: int
            sample: 2
        db_instance_memory:
            description: The memory of the instance.
            returned: always
            type: int
            sample: 4096
        db_instance_storage:
            description: The type of the instance.
            returned: always
            type: str
            sample: rds.mysql.t1.small
        db_instance_storage_type:
            description: The storage capacity of the instance.
            returned: always
            type: int
            sample: 10
        db_max_quantity:
            description: The maximum number of databases that can be created in an instance.
            returned: always
            type: int
            sample: 200
        dispense_mode:
            description: The allocation mode.
            returned: always
            type: str
            sample: ClassicDispenseMode
        expire_time:
            description: The expiration time.
            returned: always
            type: str
            sample: '2019-03-27T16:00:00Z'
        maintain_time:
            description: The maintenance period of the instance.
            returned: always
            type: str
            sample: '00:00Z-02:00Z'
        max_connections:
            description: The maximum number of concurrent connections.
            returned: always
            type: int
            sample: 60
        max_iops:
            description: The maximum number of I/O requests per second.
            returned: always
            type: int
            sample: 60
        origin_configuration:
            description: The type of the instance.
            returned: always
            type: str
            sample: rds.mysql.t1.small
        port:
            description: The private port of the instance.
            returned: always
            type: str
            sample: 3306
        read_only_dbinstance_ids:
            description: The IDs of read-only instances attached to the master instance.
            returned: always
            type: complex
            contains: 
                read_only_dbinstance_id:
                    description: The ID of a read-only instance.
                    returned: always
                    type: list
                    sample: ['rr-bpxxxxxxxxx']
        security_ipmode:
            description: The IP whitelist mode.
            returned: always
            type: complex
            sample: normal
'''

import time
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.alibaba.alicloud.plugins.module_utils.alicloud_ecs import ecs_argument_spec, rds_connect, vpc_connect

HAS_FOOTMARK = False

try:
    from footmark.exception import ECSResponseError
    HAS_FOOTMARK = True
except ImportError:
    HAS_FOOTMARK = False


def get_instance(db_instance_id, name, modules, rds):
    try:
        instances = rds.describe_db_instances()
        res = None
        for ins in instances:
            if name and ins.name != name:
                continue
            if db_instance_id and ins.id != db_instance_id:
                continue
            res = ins
        return res
    except Exception as e:
        modules.fail_json(msg="Failed to describe rds: {0}".format(e))


def main():
    argument_spec = ecs_argument_spec()
    argument_spec.update(dict(
        state=dict(default="present", choices=["present", "absent", "restart"]),
        zone_id=dict(type='str', aliases=['alicloud_zone']),
        engine=dict(type='str', choices=['MySQL', 'SQLServer', 'PostgreSQL', 'PPAS', 'MariaDB']),
        engine_version=dict(type='str'),
        db_instance_net_type=dict(type='str', choices=["Internet", "Intranet"], aliases=['instance_net_type']),
        db_instance_name=dict(type='str', aliases=['description', 'name']),
        db_instance_id=dict(type='str', aliases=['id']),
        security_ip_list=dict(type='str', aliases=['security_ips']),
        pay_type=dict(type='str', choices=["PostPaid", "PrePaid"]),
        period=dict(type='int', choices=[1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 24, 36], default=1),
        connection_mode=dict(type='str', choices=["Standard", "Safe"]),
        vswitch_id=dict(type='str'),
        private_ip_address=dict(type='str'),
        tags=dict(type='dict'),
        purge_tags=dict(type='bool', default=False),
        auto_pay=dict(type='bool', aliases=['auto_renew']),
        connection_string_prefix=dict(type='str'),
        port=dict(type='str'),
        current_connection_string=dict(type='str'),
        db_instance_class=dict(type='str', aliases=['instance_class']),
        db_instance_storage=dict(type='int', aliases=['instance_storage'])
    ))
    modules = AnsibleModule(argument_spec=argument_spec)

    if HAS_FOOTMARK is False:
        modules.fail_json(msg="Package 'footmark' required for the module ali_rds_instance.")

    rds = rds_connect(modules)
    vpc = vpc_connect(modules)

    state = modules.params['state']
    vswitch_id = modules.params['vswitch_id']
    connection_string_prefix = modules.params['connection_string_prefix']
    port = modules.params['port']
    tags = modules.params['tags']
    current_connection_string = modules.params['current_connection_string']
    db_instance_description = modules.params['db_instance_name']
    modules.params['db_instance_description'] = db_instance_description
    db_instance_class = modules.params['db_instance_class']
    db_instance_storage = modules.params['db_instance_storage']
    db_instance_id = modules.params['db_instance_id']
    pay_type = modules.params['pay_type']
    used_time = modules.params['period']
    modules.params['period'] = 'Month'
    modules.params['used_time'] = str(used_time)

    if used_time > 9:
        modules.params['period'] = 'Year'
        if used_time == 12:
            modules.params['used_time'] = '1'
        elif used_time == 24:
            modules.params['used_time'] = '2'
        else:
            modules.params['used_time'] = '3'
    if pay_type:
        modules.params['pay_type'] = pay_type.capitalize()

    current_instance = None
    changed = False

    if vswitch_id:
        modules.params['instance_network_type'] = 'VPC'
        try:
            vswitch_obj = vpc.describe_vswitch_attributes(vswitch_id=vswitch_id)
            if vswitch_obj:
                modules.params['vpc_id'] = vswitch_obj.vpc_id
        except Exception as e:
            modules.fail_json(msg=str("Unable to get vswitch, error:{0}".format(e)))

    try:
        current_instance = get_instance(db_instance_id, db_instance_description, modules, rds)
    except Exception as e:
        modules.fail_json(msg=str("Unable to describe instance, error:{0}".format(e)))

    if state == 'absent':
        if current_instance:
            if current_connection_string:
                try:
                    changed = rds.release_instance_public_connection(current_connection_string=current_connection_string, db_instance_id=current_instance.id)
                    modules.exit_json(changed=changed, instances=current_instance.get().read())
                except Exception as e:
                    modules.fail_json(msg=str("Unable to release public connection string error: {0}".format(e)))
            try:
                current_instance.delete()
                modules.exit_json(changed=True, instances={})
            except Exception as e:
                modules.fail_json(msg=str("Unable to release instance error: {0}".format(e)))
        modules.fail_json(msg=str("Unable to operate your instance, please check your instance_id and try again!"))

    if state == 'restart':
        if current_instance:
            try:
                changed = current_instance.restart()
                modules.exit_json(changed=changed, instances=current_instance.get().read())
            except Exception as e:
                modules.fail_json(msg=str("Unable to restart instance error: {0}".format(e)))
        modules.fail_json(msg=str("Unable to restart your instance, please check your instance_id and try again!"))

    if not current_instance:
        try:
            modules.params['client_token'] = "Ansible-Alicloud-%s-%s" % (hash(str(modules.params)), str(time.time()))
            current_instance = rds.create_db_instance(**modules.params)
            modules.exit_json(changed=True, instances=current_instance.get().read())
        except Exception as e:
            modules.fail_json(msg=str("Unable to create rds instance error: {0}".format(e)))

    if connection_string_prefix and port:
        if current_connection_string:
            try:
                changed = current_instance.modify_db_instance_connection_string(current_connection_string=current_connection_string, connection_string_prefix=connection_string_prefix, port=port)
                modules.exit_json(changed=changed, instances=current_instance.get().read())
            except Exception as e:
                modules.fail_json(msg=str("Unable to modify current string error: {0}".format(e)))
        else:
            try:
                changed = current_instance.allocate_public_connection_string(connection_string_prefix=connection_string_prefix, port=port)
                modules.exit_json(changed=changed, instances=current_instance.get().read())
            except Exception as e:
                modules.fail_json(msg=str("Unable to allocate public connection error: {0}".format(e)))

    if db_instance_class or db_instance_storage:
        try:
            changed = current_instance.modify_instance_spec(db_instance_class=db_instance_class, db_instance_storage=db_instance_storage)
        except Exception as e:
            modules.fail_json(msg=str("Unable to modify instance spec: {0}".format(e)))

    if modules.params['purge_tags']:
        if not tags:
            tags = current_instance.tags
        try:
            if current_instance.remove_tags(tags):
                changed = True
            modules.exit_json(changed=changed, instances=current_instance.get().read())
        except Exception as e:
            modules.fail_json(msg="{0}".format(e))

    if tags:
        try:
            if current_instance.add_tags(tags):
                changed = True
        except Exception as e:
            modules.fail_json(msg="{0}".format(e))

    modules.exit_json(changed=changed, instances=current_instance.get().read())


if __name__ == '__main__':
    main()
