Files
offline_kubespray/collection/community/aws/plugins/modules/rds.py
ByeonJungHun 360c6eef4a offline 작업
2024-02-19 16:02:29 +09:00

1401 lines
56 KiB
Python

#!/usr/bin/python
# Copyright: Ansible Project
# 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
DOCUMENTATION = r'''
---
module: rds
version_added: 1.0.0
deprecated:
removed_in: 3.0.0
why: The rds module is based upon a deprecated version of the AWS SDK.
alternative: Use M(community.aws.rds_instance), M(community.aws.rds_instance_info), and M(community.aws.rds_instance_snapshot).
short_description: create, delete, or modify Amazon rds instances, rds snapshots, and related facts
description:
- Creates, deletes, or modifies rds resources.
- When creating an instance it can be either a new instance or a read-only replica of an existing instance.
- The 'promote' command requires boto >= 2.18.0. Certain features such as tags rely on boto.rds2 (boto >= 2.26.0).
- Please use the boto3 based M(community.aws.rds_instance) instead.
options:
command:
description:
- Specifies the action to take. The 'reboot' option is available starting at version 2.0.
required: true
choices: [ 'create', 'replicate', 'delete', 'facts', 'modify' , 'promote', 'snapshot', 'reboot', 'restore' ]
type: str
instance_name:
description:
- Database instance identifier.
- Required except when using I(command=facts) or I(command=delete) on just a snapshot.
type: str
source_instance:
description:
- Name of the database to replicate.
- Used only when I(command=replicate).
type: str
db_engine:
description:
- The type of database.
- Used only when I(command=create).
- mariadb was added in version 2.2.
choices: ['mariadb', 'MySQL', 'oracle-se1', 'oracle-se2', 'oracle-se', 'oracle-ee',
'sqlserver-ee', 'sqlserver-se', 'sqlserver-ex', 'sqlserver-web', 'postgres', 'aurora']
type: str
size:
description:
- Size in gigabytes of the initial storage for the DB instance.
- Used only when I(command=create) or I(command=modify).
type: str
instance_type:
description:
- The instance type of the database.
- If not specified then the replica inherits the same instance type as the source instance.
- Required when I(command=create).
- Optional when I(command=replicate), I(command=modify) or I(command=restore).
aliases: ['type']
type: str
username:
description:
- Master database username.
- Used only when I(command=create).
type: str
password:
description:
- Password for the master database username.
- Used only when I(command=create) or I(command=modify).
type: str
db_name:
description:
- Name of a database to create within the instance.
- If not specified then no database is created.
- Used only when I(command=create).
type: str
engine_version:
description:
- Version number of the database engine to use.
- If not specified then the current Amazon RDS default engine version is used
- Used only when I(command=create).
type: str
parameter_group:
description:
- Name of the DB parameter group to associate with this instance.
- If omitted then the RDS default DBParameterGroup will be used.
- Used only when I(command=create) or I(command=modify).
type: str
license_model:
description:
- The license model for this DB instance.
- Used only when I(command=create) or I(command=restore).
choices: [ 'license-included', 'bring-your-own-license', 'general-public-license', 'postgresql-license' ]
type: str
multi_zone:
description:
- Specifies if this is a Multi-availability-zone deployment.
- Can not be used in conjunction with I(zone) parameter.
- Used only when I(command=create) or I(command=modify).
type: bool
iops:
description:
- Specifies the number of IOPS for the instance.
- Used only when I(command=create) or I(command=modify).
- Must be an integer greater than 1000.
type: str
security_groups:
description:
- Comma separated list of one or more security groups.
- Used only when I(command=create) or I(command=modify).
type: str
vpc_security_groups:
description:
- Comma separated list of one or more vpc security group ids.
- Also requires I(subnet) to be specified.
- Used only when I(command=create) or I(command=modify).
type: list
elements: str
port:
description:
- Port number that the DB instance uses for connections.
- Used only when I(command=create) or I(command=replicate).
- 'Defaults to the standard ports for each I(db_engine): C(3306) for MySQL and MariaDB, C(1521) for Oracle
C(1433) for SQL Server, C(5432) for PostgreSQL.'
type: int
upgrade:
description:
- Indicates that minor version upgrades should be applied automatically.
- Used only when I(command=create) or I(command=modify) or I(command=restore) or I(command=replicate).
type: bool
default: false
option_group:
description:
- The name of the option group to use.
- If not specified then the default option group is used.
- Used only when I(command=create).
type: str
maint_window:
description:
- 'Maintenance window in format of C(ddd:hh24:mi-ddd:hh24:mi). (Example: C(Mon:22:00-Mon:23:15))'
- Times are specified in UTC.
- If not specified then a random maintenance window is assigned.
- Used only when I(command=create) or I(command=modify).
type: str
backup_window:
description:
- 'Backup window in format of C(hh24:mi-hh24:mi). (Example: C(18:00-20:30))'
- Times are specified in UTC.
- If not specified then a random backup window is assigned.
- Used only when command=create or command=modify.
type: str
backup_retention:
description:
- Number of days backups are retained.
- Set to 0 to disable backups.
- Default is 1 day.
- 'Valid range: 0-35.'
- Used only when I(command=create) or I(command=modify).
type: str
zone:
description:
- availability zone in which to launch the instance.
- Used only when I(command=create), I(command=replicate) or I(command=restore).
- Can not be used in conjunction with I(multi_zone) parameter.
aliases: ['aws_zone', 'ec2_zone']
type: str
subnet:
description:
- VPC subnet group.
- If specified then a VPC instance is created.
- Used only when I(command=create).
type: str
snapshot:
description:
- Name of snapshot to take.
- When I(command=delete), if no I(snapshot) name is provided then no snapshot is taken.
- When I(command=delete), if no I(instance_name) is provided the snapshot is deleted.
- Used with I(command=facts), I(command=delete) or I(command=snapshot).
type: str
wait:
description:
- When I(command=create), replicate, modify or restore then wait for the database to enter the 'available' state.
- When I(command=delete), wait for the database to be terminated.
type: bool
default: false
wait_timeout:
description:
- How long before wait gives up, in seconds.
- Used when I(wait=true).
default: 300
type: int
apply_immediately:
description:
- When I(apply_immediately=true), the modifications will be applied as soon as possible rather than waiting for the
next preferred maintenance window.
- Used only when I(command=modify).
type: bool
default: false
force_failover:
description:
- If enabled, the reboot is done using a MultiAZ failover.
- Used only when I(command=reboot).
type: bool
default: false
new_instance_name:
description:
- Name to rename an instance to.
- Used only when I(command=modify).
type: str
character_set_name:
description:
- Associate the DB instance with a specified character set.
- Used with I(command=create).
type: str
publicly_accessible:
description:
- Explicitly set whether the resource should be publicly accessible or not.
- Used with I(command=create), I(command=replicate).
- Requires boto >= 2.26.0
type: str
tags:
description:
- tags dict to apply to a resource.
- Used with I(command=create), I(command=replicate), I(command=restore).
- Requires boto >= 2.26.0
type: dict
author:
- "Bruce Pennypacker (@bpennypacker)"
- "Will Thames (@willthames)"
extends_documentation_fragment:
- amazon.aws.aws
- amazon.aws.ec2
requirements:
- boto >= 2.49.0
'''
# FIXME: the command stuff needs a 'state' like alias to make things consistent -- MPD
EXAMPLES = r'''
- name: Basic mysql provisioning example
community.aws.rds:
command: create
instance_name: new-database
db_engine: MySQL
size: 10
instance_type: db.m1.small
username: mysql_admin
password: 1nsecure
tags:
Environment: testing
Application: cms
- name: Create a read-only replica and wait for it to become available
community.aws.rds:
command: replicate
instance_name: new-database-replica
source_instance: new_database
wait: yes
wait_timeout: 600
- name: Delete an instance, but create a snapshot before doing so
community.aws.rds:
command: delete
instance_name: new-database
snapshot: new_database_snapshot
- name: Get facts about an instance
community.aws.rds:
command: facts
instance_name: new-database
register: new_database_facts
- name: Rename an instance and wait for the change to take effect
community.aws.rds:
command: modify
instance_name: new-database
new_instance_name: renamed-database
wait: yes
- name: Reboot an instance and wait for it to become available again
community.aws.rds:
command: reboot
instance_name: database
wait: yes
# Restore a Postgres db instance from a snapshot, wait for it to become available again, and
# then modify it to add your security group. Also, display the new endpoint.
# Note that the "publicly_accessible" option is allowed here just as it is in the AWS CLI
- community.aws.rds:
command: restore
snapshot: mypostgres-snapshot
instance_name: MyNewInstanceName
region: us-west-2
zone: us-west-2b
subnet: default-vpc-xx441xxx
publicly_accessible: yes
wait: yes
wait_timeout: 600
tags:
Name: pg1_test_name_tag
register: rds
- community.aws.rds:
command: modify
instance_name: MyNewInstanceName
region: us-west-2
vpc_security_groups: sg-xxx945xx
- ansible.builtin.debug:
msg: "The new db endpoint is {{ rds.instance.endpoint }}"
'''
RETURN = r'''
instance:
description: the rds instance
returned: always
type: complex
contains:
engine:
description: the name of the database engine
returned: when RDS instance exists
type: str
sample: "oracle-se"
engine_version:
description: the version of the database engine
returned: when RDS instance exists
type: str
sample: "11.2.0.4.v6"
license_model:
description: the license model information
returned: when RDS instance exists
type: str
sample: "bring-your-own-license"
character_set_name:
description: the name of the character set that this instance is associated with
returned: when RDS instance exists
type: str
sample: "AL32UTF8"
allocated_storage:
description: the allocated storage size in gigabytes (GB)
returned: when RDS instance exists
type: str
sample: "100"
publicly_accessible:
description: the accessibility options for the DB instance
returned: when RDS instance exists
type: bool
sample: "true"
latest_restorable_time:
description: the latest time to which a database can be restored with point-in-time restore
returned: when RDS instance exists
type: str
sample: "1489707802.0"
secondary_availability_zone:
description: the name of the secondary AZ for a DB instance with multi-AZ support
returned: when RDS instance exists and is multi-AZ
type: str
sample: "eu-west-1b"
backup_window:
description: the daily time range during which automated backups are created if automated backups are enabled
returned: when RDS instance exists and automated backups are enabled
type: str
sample: "03:00-03:30"
auto_minor_version_upgrade:
description: indicates that minor engine upgrades will be applied automatically to the DB instance during the maintenance window
returned: when RDS instance exists
type: bool
sample: "true"
read_replica_source_dbinstance_identifier:
description: the identifier of the source DB instance if this RDS instance is a read replica
returned: when read replica RDS instance exists
type: str
sample: "null"
db_name:
description: the name of the database to create when the DB instance is created
returned: when RDS instance exists
type: str
sample: "ASERTG"
endpoint:
description: the endpoint uri of the database instance
returned: when RDS instance exists
type: str
sample: "my-ansible-database.asdfaosdgih.us-east-1.rds.amazonaws.com"
port:
description: the listening port of the database instance
returned: when RDS instance exists
type: int
sample: 3306
parameter_groups:
description: the list of DB parameter groups applied to this RDS instance
returned: when RDS instance exists and parameter groups are defined
type: complex
contains:
parameter_apply_status:
description: the status of parameter updates
returned: when RDS instance exists
type: str
sample: "in-sync"
parameter_group_name:
description: the name of the DP parameter group
returned: when RDS instance exists
type: str
sample: "testawsrpprodb01spfile-1ujg7nrs7sgyz"
option_groups:
description: the list of option group memberships for this RDS instance
returned: when RDS instance exists
type: complex
contains:
option_group_name:
description: the option group name for this RDS instance
returned: when RDS instance exists
type: str
sample: "default:oracle-se-11-2"
status:
description: the status of the RDS instance's option group membership
returned: when RDS instance exists
type: str
sample: "in-sync"
pending_modified_values:
description: a dictionary of changes to the RDS instance that are pending
returned: when RDS instance exists
type: complex
contains:
db_instance_class:
description: the new DB instance class for this RDS instance that will be applied or is in progress
returned: when RDS instance exists
type: str
sample: "null"
db_instance_identifier:
description: the new DB instance identifier this RDS instance that will be applied or is in progress
returned: when RDS instance exists
type: str
sample: "null"
allocated_storage:
description: the new allocated storage size for this RDS instance that will be applied or is in progress
returned: when RDS instance exists
type: str
sample: "null"
backup_retention_period:
description: the pending number of days for which automated backups are retained
returned: when RDS instance exists
type: str
sample: "null"
engine_version:
description: indicates the database engine version
returned: when RDS instance exists
type: str
sample: "null"
iops:
description: the new provisioned IOPS value for this RDS instance that will be applied or is being applied
returned: when RDS instance exists
type: str
sample: "null"
master_user_password:
description: the pending or in-progress change of the master credentials for this RDS instance
returned: when RDS instance exists
type: str
sample: "null"
multi_az:
description: indicates that the single-AZ RDS instance is to change to a multi-AZ deployment
returned: when RDS instance exists
type: str
sample: "null"
port:
description: specifies the pending port for this RDS instance
returned: when RDS instance exists
type: str
sample: "null"
db_subnet_groups:
description: information on the subnet group associated with this RDS instance
returned: when RDS instance exists
type: complex
contains:
description:
description: the subnet group associated with the DB instance
returned: when RDS instance exists
type: str
sample: "Subnets for the UAT RDS SQL DB Instance"
name:
description: the name of the DB subnet group
returned: when RDS instance exists
type: str
sample: "samplesubnetgrouprds-j6paiqkxqp4z"
status:
description: the status of the DB subnet group
returned: when RDS instance exists
type: str
sample: "complete"
subnets:
description: the description of the DB subnet group
returned: when RDS instance exists
type: complex
contains:
availability_zone:
description: subnet availability zone information
returned: when RDS instance exists
type: complex
contains:
name:
description: availability zone
returned: when RDS instance exists
type: str
sample: "eu-west-1b"
provisioned_iops_capable:
description: whether provisioned iops are available in AZ subnet
returned: when RDS instance exists
type: bool
sample: "false"
identifier:
description: the identifier of the subnet
returned: when RDS instance exists
type: str
sample: "subnet-3fdba63e"
status:
description: the status of the subnet
returned: when RDS instance exists
type: str
sample: "active"
'''
import time
try:
import boto.rds
import boto.exception
except ImportError:
pass # Taken care of by ec2.HAS_BOTO
try:
import boto.rds2
import boto.rds2.exceptions
HAS_RDS2 = True
except ImportError:
HAS_RDS2 = False
from ansible.module_utils._text import to_native
from ansible_collections.amazon.aws.plugins.module_utils.core import AnsibleAWSModule
from ansible_collections.amazon.aws.plugins.module_utils.ec2 import AWSRetry
from ansible_collections.amazon.aws.plugins.module_utils.ec2 import HAS_BOTO
from ansible_collections.amazon.aws.plugins.module_utils.ec2 import connect_to_aws
from ansible_collections.amazon.aws.plugins.module_utils.ec2 import get_aws_connection_info
DEFAULT_PORTS = {
'aurora': 3306,
'mariadb': 3306,
'mysql': 3306,
'oracle': 1521,
'sqlserver': 1433,
'postgres': 5432,
}
class RDSException(Exception):
def __init__(self, exc):
if hasattr(exc, 'error_message') and exc.error_message:
self.message = exc.error_message
self.code = exc.error_code
elif hasattr(exc, 'body') and 'Error' in exc.body:
self.message = exc.body['Error']['Message']
self.code = exc.body['Error']['Code']
else:
self.message = str(exc)
self.code = 'Unknown Error'
class RDSConnection:
def __init__(self, module, region, **aws_connect_params):
try:
self.connection = connect_to_aws(boto.rds, region, **aws_connect_params)
except boto.exception.BotoServerError as e:
module.fail_json(msg=e.error_message)
def get_db_instance(self, instancename):
try:
return RDSDBInstance(self.connection.get_all_dbinstances(instancename)[0])
except boto.exception.BotoServerError:
return None
def get_db_snapshot(self, snapshotid):
try:
return RDSSnapshot(self.connection.get_all_dbsnapshots(snapshot_id=snapshotid)[0])
except boto.exception.BotoServerError:
return None
def create_db_instance(self, instance_name, size, instance_class, db_engine,
username, password, **params):
params['engine'] = db_engine
try:
result = self.connection.create_dbinstance(instance_name, size, instance_class,
username, password, **params)
return RDSDBInstance(result)
except boto.exception.BotoServerError as e:
raise RDSException(e)
def create_db_instance_read_replica(self, instance_name, source_instance, **params):
try:
result = self.connection.createdb_instance_read_replica(instance_name, source_instance, **params)
return RDSDBInstance(result)
except boto.exception.BotoServerError as e:
raise RDSException(e)
def delete_db_instance(self, instance_name, **params):
try:
result = self.connection.delete_dbinstance(instance_name, **params)
return RDSDBInstance(result)
except boto.exception.BotoServerError as e:
raise RDSException(e)
def delete_db_snapshot(self, snapshot):
try:
result = self.connection.delete_dbsnapshot(snapshot)
return RDSSnapshot(result)
except boto.exception.BotoServerError as e:
raise RDSException(e)
def modify_db_instance(self, instance_name, **params):
try:
result = self.connection.modify_dbinstance(instance_name, **params)
return RDSDBInstance(result)
except boto.exception.BotoServerError as e:
raise RDSException(e)
def reboot_db_instance(self, instance_name, **params):
try:
result = self.connection.reboot_dbinstance(instance_name)
return RDSDBInstance(result)
except boto.exception.BotoServerError as e:
raise RDSException(e)
def restore_db_instance_from_db_snapshot(self, instance_name, snapshot, instance_type, **params):
try:
result = self.connection.restore_dbinstance_from_dbsnapshot(snapshot, instance_name, instance_type, **params)
return RDSDBInstance(result)
except boto.exception.BotoServerError as e:
raise RDSException(e)
def create_db_snapshot(self, snapshot, instance_name, **params):
try:
result = self.connection.create_dbsnapshot(snapshot, instance_name)
return RDSSnapshot(result)
except boto.exception.BotoServerError as e:
raise RDSException(e)
def promote_read_replica(self, instance_name, **params):
try:
result = self.connection.promote_read_replica(instance_name, **params)
return RDSDBInstance(result)
except boto.exception.BotoServerError as e:
raise RDSException(e)
class RDS2Connection:
def __init__(self, module, region, **aws_connect_params):
try:
self.connection = connect_to_aws(boto.rds2, region, **aws_connect_params)
except boto.exception.BotoServerError as e:
module.fail_json(msg=e.error_message)
def get_db_instance(self, instancename):
try:
dbinstances = self.connection.describe_db_instances(
db_instance_identifier=instancename
)['DescribeDBInstancesResponse']['DescribeDBInstancesResult']['DBInstances']
result = RDS2DBInstance(dbinstances[0])
return result
except boto.rds2.exceptions.DBInstanceNotFound as e:
return None
except Exception as e:
raise e
def get_db_snapshot(self, snapshotid):
try:
snapshots = self.connection.describe_db_snapshots(
db_snapshot_identifier=snapshotid,
snapshot_type='manual'
)['DescribeDBSnapshotsResponse']['DescribeDBSnapshotsResult']['DBSnapshots']
result = RDS2Snapshot(snapshots[0])
return result
except boto.rds2.exceptions.DBSnapshotNotFound:
return None
def create_db_instance(self, instance_name, size, instance_class, db_engine,
username, password, **params):
try:
result = self.connection.create_db_instance(instance_name, size, instance_class, db_engine, username, password,
**params)['CreateDBInstanceResponse']['CreateDBInstanceResult']['DBInstance']
return RDS2DBInstance(result)
except boto.exception.BotoServerError as e:
raise RDSException(e)
def create_db_instance_read_replica(self, instance_name, source_instance, **params):
try:
result = self.connection.create_db_instance_read_replica(
instance_name,
source_instance,
**params
)['CreateDBInstanceReadReplicaResponse']['CreateDBInstanceReadReplicaResult']['DBInstance']
return RDS2DBInstance(result)
except boto.exception.BotoServerError as e:
raise RDSException(e)
def delete_db_instance(self, instance_name, **params):
try:
result = self.connection.delete_db_instance(instance_name, **params)['DeleteDBInstanceResponse']['DeleteDBInstanceResult']['DBInstance']
return RDS2DBInstance(result)
except boto.exception.BotoServerError as e:
raise RDSException(e)
def delete_db_snapshot(self, snapshot):
try:
result = self.connection.delete_db_snapshot(snapshot)['DeleteDBSnapshotResponse']['DeleteDBSnapshotResult']['DBSnapshot']
return RDS2Snapshot(result)
except boto.exception.BotoServerError as e:
raise RDSException(e)
def modify_db_instance(self, instance_name, **params):
try:
result = self.connection.modify_db_instance(instance_name, **params)['ModifyDBInstanceResponse']['ModifyDBInstanceResult']['DBInstance']
return RDS2DBInstance(result)
except boto.exception.BotoServerError as e:
raise RDSException(e)
def reboot_db_instance(self, instance_name, **params):
try:
result = self.connection.reboot_db_instance(instance_name, **params)['RebootDBInstanceResponse']['RebootDBInstanceResult']['DBInstance']
return RDS2DBInstance(result)
except boto.exception.BotoServerError as e:
raise RDSException(e)
def restore_db_instance_from_db_snapshot(self, instance_name, snapshot, instance_type, **params):
try:
result = self.connection.restore_db_instance_from_db_snapshot(
instance_name,
snapshot,
**params
)['RestoreDBInstanceFromDBSnapshotResponse']['RestoreDBInstanceFromDBSnapshotResult']['DBInstance']
return RDS2DBInstance(result)
except boto.exception.BotoServerError as e:
raise RDSException(e)
def create_db_snapshot(self, snapshot, instance_name, **params):
try:
result = self.connection.create_db_snapshot(snapshot, instance_name, **params)['CreateDBSnapshotResponse']['CreateDBSnapshotResult']['DBSnapshot']
return RDS2Snapshot(result)
except boto.exception.BotoServerError as e:
raise RDSException(e)
def promote_read_replica(self, instance_name, **params):
try:
result = self.connection.promote_read_replica(instance_name, **params)['PromoteReadReplicaResponse']['PromoteReadReplicaResult']['DBInstance']
return RDS2DBInstance(result)
except boto.exception.BotoServerError as e:
raise RDSException(e)
class RDSDBInstance:
def __init__(self, dbinstance):
self.instance = dbinstance
self.name = dbinstance.id
self.status = dbinstance.status
def get_data(self):
d = {
'id': self.name,
'create_time': self.instance.create_time,
'status': self.status,
'availability_zone': self.instance.availability_zone,
'backup_retention': self.instance.backup_retention_period,
'backup_window': self.instance.preferred_backup_window,
'maintenance_window': self.instance.preferred_maintenance_window,
'multi_zone': self.instance.multi_az,
'instance_type': self.instance.instance_class,
'username': self.instance.master_username,
'iops': self.instance.iops
}
# Only assign an Endpoint if one is available
if hasattr(self.instance, 'endpoint'):
d["endpoint"] = self.instance.endpoint[0]
d["port"] = self.instance.endpoint[1]
if self.instance.vpc_security_groups is not None:
d["vpc_security_groups"] = ','.join(x.vpc_group for x in self.instance.vpc_security_groups)
else:
d["vpc_security_groups"] = None
else:
d["endpoint"] = None
d["port"] = None
d["vpc_security_groups"] = None
d['DBName'] = self.instance.DBName if hasattr(self.instance, 'DBName') else None
# ReadReplicaSourceDBInstanceIdentifier may or may not exist
try:
d["replication_source"] = self.instance.ReadReplicaSourceDBInstanceIdentifier
except Exception:
d["replication_source"] = None
return d
class RDS2DBInstance:
def __init__(self, dbinstance):
self.instance = dbinstance
if 'DBInstanceIdentifier' not in dbinstance:
self.name = None
else:
self.name = self.instance.get('DBInstanceIdentifier')
self.status = self.instance.get('DBInstanceStatus')
def get_data(self):
d = {
'id': self.name,
'create_time': self.instance['InstanceCreateTime'],
'engine': self.instance['Engine'],
'engine_version': self.instance['EngineVersion'],
'license_model': self.instance['LicenseModel'],
'character_set_name': self.instance['CharacterSetName'],
'allocated_storage': self.instance['AllocatedStorage'],
'publicly_accessible': self.instance['PubliclyAccessible'],
'latest_restorable_time': self.instance['LatestRestorableTime'],
'status': self.status,
'availability_zone': self.instance['AvailabilityZone'],
'secondary_availability_zone': self.instance['SecondaryAvailabilityZone'],
'backup_retention': self.instance['BackupRetentionPeriod'],
'backup_window': self.instance['PreferredBackupWindow'],
'maintenance_window': self.instance['PreferredMaintenanceWindow'],
'auto_minor_version_upgrade': self.instance['AutoMinorVersionUpgrade'],
'read_replica_source_dbinstance_identifier': self.instance['ReadReplicaSourceDBInstanceIdentifier'],
'multi_zone': self.instance['MultiAZ'],
'instance_type': self.instance['DBInstanceClass'],
'username': self.instance['MasterUsername'],
'db_name': self.instance['DBName'],
'iops': self.instance['Iops'],
'replication_source': self.instance['ReadReplicaSourceDBInstanceIdentifier']
}
if self.instance['DBParameterGroups'] is not None:
parameter_groups = []
for x in self.instance['DBParameterGroups']:
parameter_groups.append({'parameter_group_name': x['DBParameterGroupName'], 'parameter_apply_status': x['ParameterApplyStatus']})
d['parameter_groups'] = parameter_groups
if self.instance['OptionGroupMemberships'] is not None:
option_groups = []
for x in self.instance['OptionGroupMemberships']:
option_groups.append({'status': x['Status'], 'option_group_name': x['OptionGroupName']})
d['option_groups'] = option_groups
if self.instance['PendingModifiedValues'] is not None:
pdv = self.instance['PendingModifiedValues']
d['pending_modified_values'] = {
'multi_az': pdv['MultiAZ'],
'master_user_password': pdv['MasterUserPassword'],
'port': pdv['Port'],
'iops': pdv['Iops'],
'allocated_storage': pdv['AllocatedStorage'],
'engine_version': pdv['EngineVersion'],
'backup_retention_period': pdv['BackupRetentionPeriod'],
'db_instance_class': pdv['DBInstanceClass'],
'db_instance_identifier': pdv['DBInstanceIdentifier']
}
if self.instance["DBSubnetGroup"] is not None:
dsg = self.instance["DBSubnetGroup"]
db_subnet_groups = {}
db_subnet_groups['vpc_id'] = dsg['VpcId']
db_subnet_groups['name'] = dsg['DBSubnetGroupName']
db_subnet_groups['status'] = dsg['SubnetGroupStatus'].lower()
db_subnet_groups['description'] = dsg['DBSubnetGroupDescription']
db_subnet_groups['subnets'] = []
for x in dsg["Subnets"]:
db_subnet_groups['subnets'].append({
'status': x['SubnetStatus'].lower(),
'identifier': x['SubnetIdentifier'],
'availability_zone': {
'name': x['SubnetAvailabilityZone']['Name'],
'provisioned_iops_capable': x['SubnetAvailabilityZone']['ProvisionedIopsCapable']
}
})
d['db_subnet_groups'] = db_subnet_groups
if self.instance["VpcSecurityGroups"] is not None:
d['vpc_security_groups'] = ','.join(x['VpcSecurityGroupId'] for x in self.instance['VpcSecurityGroups'])
if "Endpoint" in self.instance and self.instance["Endpoint"] is not None:
d['endpoint'] = self.instance["Endpoint"].get('Address', None)
d['port'] = self.instance["Endpoint"].get('Port', None)
else:
d['endpoint'] = None
d['port'] = None
d['DBName'] = self.instance['DBName'] if hasattr(self.instance, 'DBName') else None
return d
class RDSSnapshot:
def __init__(self, snapshot):
self.snapshot = snapshot
self.name = snapshot.id
self.status = snapshot.status
def get_data(self):
d = {
'id': self.name,
'create_time': self.snapshot.snapshot_create_time,
'status': self.status,
'availability_zone': self.snapshot.availability_zone,
'instance_id': self.snapshot.instance_id,
'instance_created': self.snapshot.instance_create_time,
}
# needs boto >= 2.21.0
if hasattr(self.snapshot, 'snapshot_type'):
d["snapshot_type"] = self.snapshot.snapshot_type
if hasattr(self.snapshot, 'iops'):
d["iops"] = self.snapshot.iops
return d
class RDS2Snapshot:
def __init__(self, snapshot):
if 'DeleteDBSnapshotResponse' in snapshot:
self.snapshot = snapshot['DeleteDBSnapshotResponse']['DeleteDBSnapshotResult']['DBSnapshot']
else:
self.snapshot = snapshot
self.name = self.snapshot.get('DBSnapshotIdentifier')
self.status = self.snapshot.get('Status')
def get_data(self):
d = {
'id': self.name,
'create_time': self.snapshot['SnapshotCreateTime'],
'status': self.status,
'availability_zone': self.snapshot['AvailabilityZone'],
'instance_id': self.snapshot['DBInstanceIdentifier'],
'instance_created': self.snapshot['InstanceCreateTime'],
'snapshot_type': self.snapshot['SnapshotType'],
'iops': self.snapshot['Iops'],
}
return d
def await_resource(conn, resource, status, module):
start_time = time.time()
wait_timeout = module.params.get('wait_timeout') + start_time
check_interval = 5
while wait_timeout > time.time() and resource.status != status:
time.sleep(check_interval)
if wait_timeout <= time.time():
module.fail_json(msg="Timeout waiting for RDS resource %s" % resource.name)
if module.params.get('command') == 'snapshot':
# Temporary until all the rds2 commands have their responses parsed
if resource.name is None:
module.fail_json(msg="There was a problem waiting for RDS snapshot %s" % resource.snapshot)
# Back off if we're getting throttled, since we're just waiting anyway
resource = AWSRetry.jittered_backoff(retries=5, delay=20, backoff=1.5)(conn.get_db_snapshot)(resource.name)
else:
# Temporary until all the rds2 commands have their responses parsed
if resource.name is None:
module.fail_json(msg="There was a problem waiting for RDS instance %s" % resource.instance)
# Back off if we're getting throttled, since we're just waiting anyway
resource = AWSRetry.jittered_backoff(retries=5, delay=20, backoff=1.5)(conn.get_db_instance)(resource.name)
if resource is None:
break
# Some RDS resources take much longer than others to be ready. Check
# less aggressively for slow ones to avoid throttling.
if time.time() > start_time + 90:
check_interval = 20
return resource
def create_db_instance(module, conn):
required_vars = ['instance_name', 'db_engine', 'size', 'instance_type', 'username', 'password']
valid_vars = ['backup_retention', 'backup_window',
'character_set_name', 'db_name', 'engine_version',
'instance_type', 'iops', 'license_model', 'maint_window',
'multi_zone', 'option_group', 'parameter_group', 'port',
'subnet', 'upgrade', 'zone']
if module.params.get('subnet'):
valid_vars.append('vpc_security_groups')
else:
valid_vars.append('security_groups')
if HAS_RDS2:
valid_vars.extend(['publicly_accessible', 'tags'])
params = validate_parameters(required_vars, valid_vars, module)
instance_name = module.params.get('instance_name')
result = conn.get_db_instance(instance_name)
if result:
changed = False
else:
try:
result = conn.create_db_instance(instance_name, module.params.get('size'),
module.params.get('instance_type'), module.params.get('db_engine'),
module.params.get('username'), module.params.get('password'), **params)
changed = True
except RDSException as e:
module.fail_json(msg="Failed to create instance: %s" % to_native(e))
if module.params.get('wait'):
resource = await_resource(conn, result, 'available', module)
else:
resource = conn.get_db_instance(instance_name)
module.exit_json(changed=changed, instance=resource.get_data())
def replicate_db_instance(module, conn):
required_vars = ['instance_name', 'source_instance']
valid_vars = ['instance_type', 'port', 'upgrade', 'zone']
if HAS_RDS2:
valid_vars.extend(['iops', 'option_group', 'publicly_accessible', 'tags'])
params = validate_parameters(required_vars, valid_vars, module)
instance_name = module.params.get('instance_name')
source_instance = module.params.get('source_instance')
result = conn.get_db_instance(instance_name)
if result:
changed = False
else:
try:
result = conn.create_db_instance_read_replica(instance_name, source_instance, **params)
changed = True
except RDSException as e:
module.fail_json(msg="Failed to create replica instance: %s " % to_native(e))
if module.params.get('wait'):
resource = await_resource(conn, result, 'available', module)
else:
resource = conn.get_db_instance(instance_name)
module.exit_json(changed=changed, instance=resource.get_data())
def delete_db_instance_or_snapshot(module, conn):
required_vars = []
valid_vars = ['instance_name', 'snapshot', 'skip_final_snapshot']
params = validate_parameters(required_vars, valid_vars, module)
instance_name = module.params.get('instance_name')
snapshot = module.params.get('snapshot')
if not instance_name:
result = conn.get_db_snapshot(snapshot)
else:
result = conn.get_db_instance(instance_name)
if not result:
module.exit_json(changed=False)
if result.status == 'deleting':
module.exit_json(changed=False)
try:
if instance_name:
if snapshot:
params["skip_final_snapshot"] = False
if HAS_RDS2:
params["final_db_snapshot_identifier"] = snapshot
else:
params["final_snapshot_id"] = snapshot
else:
params["skip_final_snapshot"] = True
result = conn.delete_db_instance(instance_name, **params)
else:
result = conn.delete_db_snapshot(snapshot)
except RDSException as e:
module.fail_json(msg="Failed to delete instance: %s" % to_native(e))
# If we're not waiting for a delete to complete then we're all done
# so just return
if not module.params.get('wait'):
module.exit_json(changed=True)
try:
await_resource(conn, result, 'deleted', module)
module.exit_json(changed=True)
except RDSException as e:
if e.code == 'DBInstanceNotFound':
module.exit_json(changed=True)
else:
module.fail_json(msg=to_native(e))
except Exception as e:
module.fail_json(msg=str(e))
def facts_db_instance_or_snapshot(module, conn):
instance_name = module.params.get('instance_name')
snapshot = module.params.get('snapshot')
if instance_name and snapshot:
module.fail_json(msg="Facts must be called with either instance_name or snapshot, not both")
if instance_name:
resource = conn.get_db_instance(instance_name)
if not resource:
module.fail_json(msg="DB instance %s does not exist" % instance_name)
if snapshot:
resource = conn.get_db_snapshot(snapshot)
if not resource:
module.fail_json(msg="DB snapshot %s does not exist" % snapshot)
module.exit_json(changed=False, instance=resource.get_data())
def modify_db_instance(module, conn):
required_vars = ['instance_name']
valid_vars = ['apply_immediately', 'backup_retention', 'backup_window',
'db_name', 'engine_version', 'instance_type', 'iops', 'license_model',
'maint_window', 'multi_zone', 'new_instance_name',
'option_group', 'parameter_group', 'password', 'size', 'upgrade']
params = validate_parameters(required_vars, valid_vars, module)
instance_name = module.params.get('instance_name')
new_instance_name = module.params.get('new_instance_name')
try:
result = conn.modify_db_instance(instance_name, **params)
except RDSException as e:
module.fail_json(msg=to_native(e))
if params.get('apply_immediately'):
if new_instance_name:
# Wait until the new instance name is valid
new_instance = None
while not new_instance:
new_instance = conn.get_db_instance(new_instance_name)
time.sleep(5)
# Found instance but it briefly flicks to available
# before rebooting so let's wait until we see it rebooting
# before we check whether to 'wait'
result = await_resource(conn, new_instance, 'rebooting', module)
if module.params.get('wait'):
resource = await_resource(conn, result, 'available', module)
else:
resource = conn.get_db_instance(instance_name)
# guess that this changed the DB, need a way to check
module.exit_json(changed=True, instance=resource.get_data())
def promote_db_instance(module, conn):
required_vars = ['instance_name']
valid_vars = ['backup_retention', 'backup_window']
params = validate_parameters(required_vars, valid_vars, module)
instance_name = module.params.get('instance_name')
result = conn.get_db_instance(instance_name)
if not result:
module.fail_json(msg="DB Instance %s does not exist" % instance_name)
if result.get_data().get('replication_source'):
try:
result = conn.promote_read_replica(instance_name, **params)
changed = True
except RDSException as e:
module.fail_json(msg=to_native(e))
else:
changed = False
if module.params.get('wait'):
resource = await_resource(conn, result, 'available', module)
else:
resource = conn.get_db_instance(instance_name)
module.exit_json(changed=changed, instance=resource.get_data())
def snapshot_db_instance(module, conn):
required_vars = ['instance_name', 'snapshot']
valid_vars = ['tags']
params = validate_parameters(required_vars, valid_vars, module)
instance_name = module.params.get('instance_name')
snapshot = module.params.get('snapshot')
changed = False
result = conn.get_db_snapshot(snapshot)
if not result:
try:
result = conn.create_db_snapshot(snapshot, instance_name, **params)
changed = True
except RDSException as e:
module.fail_json(msg=to_native(e))
if module.params.get('wait'):
resource = await_resource(conn, result, 'available', module)
else:
resource = conn.get_db_snapshot(snapshot)
module.exit_json(changed=changed, snapshot=resource.get_data())
def reboot_db_instance(module, conn):
required_vars = ['instance_name']
valid_vars = []
if HAS_RDS2:
valid_vars.append('force_failover')
params = validate_parameters(required_vars, valid_vars, module)
instance_name = module.params.get('instance_name')
result = conn.get_db_instance(instance_name)
changed = False
try:
result = conn.reboot_db_instance(instance_name, **params)
changed = True
except RDSException as e:
module.fail_json(msg=to_native(e))
if module.params.get('wait'):
resource = await_resource(conn, result, 'available', module)
else:
resource = conn.get_db_instance(instance_name)
module.exit_json(changed=changed, instance=resource.get_data())
def restore_db_instance(module, conn):
required_vars = ['instance_name', 'snapshot']
valid_vars = ['db_name', 'iops', 'license_model', 'multi_zone',
'option_group', 'port', 'publicly_accessible',
'subnet', 'tags', 'upgrade', 'zone']
if HAS_RDS2:
valid_vars.append('instance_type')
else:
required_vars.append('instance_type')
params = validate_parameters(required_vars, valid_vars, module)
instance_name = module.params.get('instance_name')
instance_type = module.params.get('instance_type')
snapshot = module.params.get('snapshot')
changed = False
result = conn.get_db_instance(instance_name)
if not result:
try:
result = conn.restore_db_instance_from_db_snapshot(instance_name, snapshot, instance_type, **params)
changed = True
except RDSException as e:
module.fail_json(msg=to_native(e))
if module.params.get('wait'):
resource = await_resource(conn, result, 'available', module)
else:
resource = conn.get_db_instance(instance_name)
module.exit_json(changed=changed, instance=resource.get_data())
def validate_parameters(required_vars, valid_vars, module):
command = module.params.get('command')
for v in required_vars:
if not module.params.get(v):
module.fail_json(msg="Parameter %s required for %s command" % (v, command))
# map to convert rds module options to boto rds and rds2 options
optional_params = {
'port': 'port',
'db_name': 'db_name',
'zone': 'availability_zone',
'maint_window': 'preferred_maintenance_window',
'backup_window': 'preferred_backup_window',
'backup_retention': 'backup_retention_period',
'multi_zone': 'multi_az',
'engine_version': 'engine_version',
'upgrade': 'auto_minor_version_upgrade',
'subnet': 'db_subnet_group_name',
'license_model': 'license_model',
'option_group': 'option_group_name',
'size': 'allocated_storage',
'iops': 'iops',
'new_instance_name': 'new_instance_id',
'apply_immediately': 'apply_immediately',
}
# map to convert rds module options to boto rds options
optional_params_rds = {
'db_engine': 'engine',
'password': 'master_password',
'parameter_group': 'param_group',
'instance_type': 'instance_class',
}
# map to convert rds module options to boto rds2 options
optional_params_rds2 = {
'tags': 'tags',
'publicly_accessible': 'publicly_accessible',
'parameter_group': 'db_parameter_group_name',
'character_set_name': 'character_set_name',
'instance_type': 'db_instance_class',
'password': 'master_user_password',
'new_instance_name': 'new_db_instance_identifier',
'force_failover': 'force_failover',
}
if HAS_RDS2:
optional_params.update(optional_params_rds2)
sec_group = 'db_security_groups'
else:
optional_params.update(optional_params_rds)
sec_group = 'security_groups'
# Check for options only supported with rds2
for k in set(optional_params_rds2.keys()) - set(optional_params_rds.keys()):
if module.params.get(k):
module.fail_json(msg="Parameter %s requires boto.rds (boto >= 2.26.0)" % k)
params = {}
for (k, v) in optional_params.items():
if module.params.get(k) is not None and k not in required_vars:
if k in valid_vars:
params[v] = module.params[k]
else:
if module.params.get(k) is False:
pass
else:
module.fail_json(msg="Parameter %s is not valid for %s command" % (k, command))
if module.params.get('security_groups'):
params[sec_group] = module.params.get('security_groups').split(',')
vpc_groups = module.params.get('vpc_security_groups')
if vpc_groups:
if HAS_RDS2:
params['vpc_security_group_ids'] = vpc_groups
else:
groups_list = []
for x in vpc_groups:
groups_list.append(boto.rds.VPCSecurityGroupMembership(vpc_group=x))
params['vpc_security_groups'] = groups_list
# Convert tags dict to list of tuples that rds2 expects
if 'tags' in params:
params['tags'] = module.params['tags'].items()
return params
def main():
argument_spec = dict(
command=dict(choices=['create', 'replicate', 'delete', 'facts', 'modify', 'promote', 'snapshot', 'reboot', 'restore'], required=True),
instance_name=dict(required=False),
source_instance=dict(required=False),
db_engine=dict(choices=['mariadb', 'MySQL', 'oracle-se1', 'oracle-se2', 'oracle-se', 'oracle-ee', 'sqlserver-ee', 'sqlserver-se', 'sqlserver-ex',
'sqlserver-web', 'postgres', 'aurora'], required=False),
size=dict(required=False),
instance_type=dict(aliases=['type'], required=False),
username=dict(required=False),
password=dict(no_log=True, required=False),
db_name=dict(required=False),
engine_version=dict(required=False),
parameter_group=dict(required=False),
license_model=dict(choices=['license-included', 'bring-your-own-license', 'general-public-license', 'postgresql-license'], required=False),
multi_zone=dict(type='bool', required=False),
iops=dict(required=False),
security_groups=dict(required=False),
vpc_security_groups=dict(type='list', required=False, elements='str'),
port=dict(required=False, type='int'),
upgrade=dict(type='bool', default=False),
option_group=dict(required=False),
maint_window=dict(required=False),
backup_window=dict(required=False),
backup_retention=dict(required=False),
zone=dict(aliases=['aws_zone', 'ec2_zone'], required=False),
subnet=dict(required=False),
wait=dict(type='bool', default=False),
wait_timeout=dict(type='int', default=300),
snapshot=dict(required=False),
apply_immediately=dict(type='bool', default=False),
new_instance_name=dict(required=False),
tags=dict(type='dict', required=False),
publicly_accessible=dict(required=False),
character_set_name=dict(required=False),
force_failover=dict(type='bool', required=False, default=False),
)
module = AnsibleAWSModule(
argument_spec=argument_spec,
check_boto3=False,
)
module.deprecate("The 'rds' module has been deprecated and replaced by the 'rds_instance' module'",
version='3.0.0', collection_name='community.aws')
if not HAS_BOTO:
module.fail_json(msg='boto required for this module')
invocations = {
'create': create_db_instance,
'replicate': replicate_db_instance,
'delete': delete_db_instance_or_snapshot,
'facts': facts_db_instance_or_snapshot,
'modify': modify_db_instance,
'promote': promote_db_instance,
'snapshot': snapshot_db_instance,
'reboot': reboot_db_instance,
'restore': restore_db_instance,
}
region, ec2_url, aws_connect_params = get_aws_connection_info(module)
if not region:
module.fail_json(msg="Region must be specified as a parameter, in EC2_REGION or AWS_REGION environment variables or in boto configuration file")
# set port to per db defaults if not specified
if module.params['port'] is None and module.params['db_engine'] is not None and module.params['command'] == 'create':
if '-' in module.params['db_engine']:
engine = module.params['db_engine'].split('-')[0]
else:
engine = module.params['db_engine']
module.params['port'] = DEFAULT_PORTS[engine.lower()]
# connect to the rds endpoint
if HAS_RDS2:
conn = RDS2Connection(module, region, **aws_connect_params)
else:
conn = RDSConnection(module, region, **aws_connect_params)
invocations[module.params.get('command')](module, conn)
if __name__ == '__main__':
main()