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

# Copyright (c) 2017-present Alibaba Group Holding Limited. <NAME> <<EMAIL>>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#
#  This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see http://www.gnu.org/licenses/.

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:
    - "<NAME> (@xiaozhu36)"
    - "<NAME> (@lixue323)"
requirements:
    - "python >= 3.6"
    - "footmark >= 1.16.0"
extends_documentation_fragment:
    - alicloud
'''

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

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

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

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

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

- name: Changed. modify instance spec
  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
  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
  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.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'] = "<PASSWORD>" % (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()

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

# Copyright (c) 2017-present Alibaba Group Holding Limited. <NAME> <<EMAIL>>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#
#  This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see http://www.gnu.org/licenses/.

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:
    - "<NAME> (@xiaozhu36)"
    - "<NAME> (@lixue323)"
requirements:
    - "python >= 3.6"
    - "footmark >= 1.16.0"
extends_documentation_fragment:
    - alicloud
'''

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

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

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

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

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

- name: Changed. modify instance spec
  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
  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
  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.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'] = "<PASSWORD>" % (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()

#!/usr/bin/python # -*- coding: utf-8 -*- # Copyright (c) 2017-present Alibaba Group Holding Limited. <NAME> <<EMAIL>> # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # #  This file is part of Ansible # # Ansible is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # Ansible is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Ansible. If not, see http://www.gnu.org/licenses/. --- 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:     - "<NAME> (@xiaozhu36)"     - "<NAME> (@lixue323)" requirements:     - "python >= 3.6"     - "footmark >= 1.16.0" extends_documentation_fragment:     - alicloud - name: Changed. Add Tags.   ali_rds_instance:     db_instance_description: '{{ name }}'     tags:       TAG: "add1"       TAG2: "add2"  - name: Changed. Removing tags.   ali_rds_instance:     db_instance_description: '{{ name }}'     purge_tags: True     tags:       TAG: "add1"  - name: Changed. allocate instance public connection string   ali_rds_instance:     db_instance_description: '{{ name }}'     connection_string_prefix: publicave-89asd     port: 3165  - name: release instance public connection string   ali_rds_instance:     state: absent     db_instance_description: '{{ name }}'     current_connection_string: publicave-89asd.mysql.rds.aliyuncs.com  - name: restart rds instance   ali_rds_instance:     db_instance_description: '{{ name }}'     state: restart  - name: Changed. modify instance spec   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   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   ali_rds_instance:     state: absent     db_instance_description: '{{ name }}' 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