Various RDS, RDS/Cloudformation, RDS/KMS improvements. (#789)

We need to mock out deploying RDS instances with full disk encryption
and detailed tagging. We also need to be able do deploy these instances
with Cloudformation, and then access them with both boto and boto3.

* Join RDS and RDS2 backends - this makes RDS resources created via
  either of the two boto RDS APIs visible to both, more closely
  mirroring how AWS works
* Fix RDS responses that were returning JSON but should be returning XML
* Add mocking of RDS Cloudformation calls
* Add mocking of RDS full disk encryption with KMS
* Add mocking of RDS DBParameterGroups
* Fix mocking of RDS DBSecurityGroupIngress rules
* Make mocking of RDS OptionGroupOptions more accurate
* Fix mocking of RDS cross-region DB replication
* Add RDS tag support to:
  * DBs
  * DBSubnetGroups
  * DBSecurityGroups

Signed-off-by: Andrew Garrett <andrew.garrett@getbraintree.com>
This commit is contained in:
Michael Nussbaum 2017-01-11 18:02:51 -08:00 committed by Steve Pulec
parent 201efd5773
commit 74bbd9c8e5
11 changed files with 1868 additions and 721 deletions

View File

@ -14,6 +14,7 @@ from moto.elb import models as elb_models
from moto.iam import models as iam_models
from moto.kms import models as kms_models
from moto.rds import models as rds_models
from moto.rds2 import models as rds2_models
from moto.redshift import models as redshift_models
from moto.route53 import models as route53_models
from moto.s3 import models as s3_models
@ -56,6 +57,7 @@ MODEL_MAP = {
"AWS::RDS::DBInstance": rds_models.Database,
"AWS::RDS::DBSecurityGroup": rds_models.SecurityGroup,
"AWS::RDS::DBSubnetGroup": rds_models.SubnetGroup,
"AWS::RDS::DBParameterGroup": rds2_models.DBParameterGroup,
"AWS::Redshift::Cluster": redshift_models.Cluster,
"AWS::Redshift::ClusterParameterGroup": redshift_models.ParameterGroup,
"AWS::Redshift::ClusterSubnetGroup": redshift_models.SubnetGroup,
@ -311,6 +313,7 @@ class ResourceMap(collections.Mapping):
if not resource_json:
raise KeyError(resource_logical_id)
new_resource = parse_and_create_resource(resource_logical_id, resource_json, self, self._region_name)
if new_resource is not None:
self._parsed_resources[resource_logical_id] = new_resource
return new_resource

View File

@ -10,6 +10,7 @@ from moto.cloudformation.exceptions import UnformattedGetAttTemplateException
from moto.core import BaseBackend
from moto.core.utils import get_random_hex
from moto.ec2.models import ec2_backends
from moto.rds2.models import rds2_backends
from .exceptions import DBInstanceNotFoundError, DBSecurityGroupNotFoundError, DBSubnetGroupNotFoundError
@ -26,6 +27,11 @@ class Database(object):
if self.engine_version is None:
self.engine_version = "5.6.21"
self.iops = kwargs.get("iops")
self.storage_encrypted = kwargs.get("storage_encrypted", False)
if self.storage_encrypted:
self.kms_key_id = kwargs.get("kms_key_id", "default_kms_key_id")
else:
self.kms_key_id = kwargs.get("kms_key_id")
self.storage_type = kwargs.get("storage_type")
self.master_username = kwargs.get('master_username')
self.master_password = kwargs.get('master_password')
@ -119,6 +125,7 @@ class Database(object):
"engine": properties.get("Engine"),
"engine_version": properties.get("EngineVersion"),
"iops": properties.get("Iops"),
"kms_key_id": properties.get("KmsKeyId"),
"master_password": properties.get('MasterUserPassword'),
"master_username": properties.get('MasterUsername'),
"multi_az": properties.get("MultiAZ"),
@ -126,7 +133,9 @@ class Database(object):
"publicly_accessible": properties.get("PubliclyAccessible"),
"region": region_name,
"security_groups": security_groups,
"storage_encrypted": properties.get("StorageEncrypted"),
"storage_type": properties.get("StorageType"),
"tags": properties.get("Tags"),
}
rds_backend = rds_backends[region_name]
@ -204,6 +213,10 @@ class Database(object):
<PubliclyAccessible>{{ database.publicly_accessible }}</PubliclyAccessible>
<AutoMinorVersionUpgrade>{{ database.auto_minor_version_upgrade }}</AutoMinorVersionUpgrade>
<AllocatedStorage>{{ database.allocated_storage }}</AllocatedStorage>
<StorageEncrypted>{{ database.storage_encrypted }}</StorageEncrypted>
{% if database.kms_key_id %}
<KmsKeyId>{{ database.kms_key_id }}</KmsKeyId>
{% endif %}
{% if database.iops %}
<Iops>{{ database.iops }}</Iops>
<StorageType>io1</StorageType>
@ -220,6 +233,10 @@ class Database(object):
</DBInstance>""")
return template.render(database=self)
def delete(self, region_name):
backend = rds_backends[region_name]
backend.delete_database(self.db_instance_identifier)
class SecurityGroup(object):
def __init__(self, group_name, description):
@ -267,14 +284,18 @@ class SecurityGroup(object):
properties = cloudformation_json['Properties']
group_name = resource_name.lower() + get_random_hex(12)
description = properties['GroupDescription']
security_group_ingress = properties['DBSecurityGroupIngress']
security_group_ingress_rules = properties.get('DBSecurityGroupIngress', [])
tags = properties.get('Tags')
ec2_backend = ec2_backends[region_name]
rds_backend = rds_backends[region_name]
security_group = rds_backend.create_security_group(
group_name,
description,
tags,
)
for security_group_ingress in security_group_ingress_rules:
for ingress_type, ingress_value in security_group_ingress.items():
if ingress_type == "CIDRIP":
security_group.authorize_cidr(ingress_value)
@ -286,6 +307,10 @@ class SecurityGroup(object):
security_group.authorize_security_group(subnet)
return security_group
def delete(self, region_name):
backend = rds_backends[region_name]
backend.delete_security_group(self.group_name)
class SubnetGroup(object):
def __init__(self, subnet_name, description, subnets):
@ -324,6 +349,7 @@ class SubnetGroup(object):
subnet_name = resource_name.lower() + get_random_hex(12)
description = properties['DBSubnetGroupDescription']
subnet_ids = properties['SubnetIds']
tags = properties.get('Tags')
ec2_backend = ec2_backends[region_name]
subnets = [ec2_backend.get_subnet(subnet_id) for subnet_id in subnet_ids]
@ -332,102 +358,31 @@ class SubnetGroup(object):
subnet_name,
description,
subnets,
tags,
)
return subnet_group
def delete(self, region_name):
backend = rds_backends[region_name]
backend.delete_subnet_group(self.subnet_name)
class RDSBackend(BaseBackend):
def __init__(self):
self.databases = {}
self.security_groups = {}
self.subnet_groups = {}
def __init__(self, region):
self.region = region
def create_database(self, db_kwargs):
database_id = db_kwargs['db_instance_identifier']
database = Database(**db_kwargs)
self.databases[database_id] = database
return database
def __getattr__(self, attr):
return self.rds2_backend().__getattribute__(attr)
def create_database_replica(self, db_kwargs):
database_id = db_kwargs['db_instance_identifier']
source_database_id = db_kwargs['source_db_identifier']
primary = self.describe_databases(source_database_id)[0]
replica = copy.deepcopy(primary)
replica.update(db_kwargs)
replica.set_as_replica()
self.databases[database_id] = replica
primary.add_replica(replica)
return replica
def reset(self):
# preserve region
region = self.region
self.rds2_backend().reset()
self.__dict__ = {}
self.__init__(region)
def describe_databases(self, db_instance_identifier=None):
if db_instance_identifier:
if db_instance_identifier in self.databases:
return [self.databases[db_instance_identifier]]
else:
raise DBInstanceNotFoundError(db_instance_identifier)
return self.databases.values()
def rds2_backend(self):
return rds2_backends[self.region]
def modify_database(self, db_instance_identifier, db_kwargs):
database = self.describe_databases(db_instance_identifier)[0]
database.update(db_kwargs)
return database
def delete_database(self, db_instance_identifier):
if db_instance_identifier in self.databases:
database = self.databases.pop(db_instance_identifier)
if database.is_replica:
primary = self.describe_databases(database.source_db_identifier)[0]
primary.remove_replica(database)
database.status = 'deleting'
return database
else:
raise DBInstanceNotFoundError(db_instance_identifier)
def create_security_group(self, group_name, description):
security_group = SecurityGroup(group_name, description)
self.security_groups[group_name] = security_group
return security_group
def describe_security_groups(self, security_group_name):
if security_group_name:
if security_group_name in self.security_groups:
return [self.security_groups[security_group_name]]
else:
raise DBSecurityGroupNotFoundError(security_group_name)
return self.security_groups.values()
def delete_security_group(self, security_group_name):
if security_group_name in self.security_groups:
return self.security_groups.pop(security_group_name)
else:
raise DBSecurityGroupNotFoundError(security_group_name)
def authorize_security_group(self, security_group_name, cidr_ip):
security_group = self.describe_security_groups(security_group_name)[0]
security_group.authorize_cidr(cidr_ip)
return security_group
def create_subnet_group(self, subnet_name, description, subnets):
subnet_group = SubnetGroup(subnet_name, description, subnets)
self.subnet_groups[subnet_name] = subnet_group
return subnet_group
def describe_subnet_groups(self, subnet_group_name):
if subnet_group_name:
if subnet_group_name in self.subnet_groups:
return [self.subnet_groups[subnet_group_name]]
else:
raise DBSubnetGroupNotFoundError(subnet_group_name)
return self.subnet_groups.values()
def delete_subnet_group(self, subnet_name):
if subnet_name in self.subnet_groups:
return self.subnet_groups.pop(subnet_name)
else:
raise DBSubnetGroupNotFoundError(subnet_name)
rds_backends = {}
for region in boto.rds.regions():
rds_backends[region.name] = RDSBackend()
rds_backends = dict((region.name, RDSBackend(region.name)) for region in boto.rds.regions())

View File

@ -12,7 +12,7 @@ class RDSResponse(BaseResponse):
return rds_backends[self.region]
def _get_db_kwargs(self):
return {
args = {
"auto_minor_version_upgrade": self._get_param('AutoMinorVersionUpgrade'),
"allocated_storage": self._get_int_param('AllocatedStorage'),
"availability_zone": self._get_param("AvailabilityZone"),
@ -25,6 +25,7 @@ class RDSResponse(BaseResponse):
"engine": self._get_param("Engine"),
"engine_version": self._get_param("EngineVersion"),
"iops": self._get_int_param("Iops"),
"kms_key_id": self._get_param("KmsKeyId"),
"master_password": self._get_param('MasterUserPassword'),
"master_username": self._get_param('MasterUsername'),
"multi_az": self._get_bool_param("MultiAZ"),
@ -35,9 +36,13 @@ class RDSResponse(BaseResponse):
"publicly_accessible": self._get_param("PubliclyAccessible"),
"region": self.region,
"security_groups": self._get_multi_param('DBSecurityGroups.member'),
"storage_encrypted": self._get_param("StorageEncrypted"),
"storage_type": self._get_param("StorageType"),
# VpcSecurityGroupIds.member.N
"tags": list(),
}
args['tags'] = self.unpack_complex_list_params('Tags.Tag', ('Key', 'Value'))
return args
def _get_db_replica_kwargs(self):
return {
@ -54,6 +59,17 @@ class RDSResponse(BaseResponse):
"storage_type": self._get_param("StorageType"),
}
def unpack_complex_list_params(self, label, names):
unpacked_list = list()
count = 1
while self._get_param('{0}.{1}.{2}'.format(label, count, names[0])):
param = dict()
for i in range(len(names)):
param[names[i]] = self._get_param('{0}.{1}.{2}'.format(label, count, names[i]))
unpacked_list.append(param)
count += 1
return unpacked_list
def create_dbinstance(self):
db_kwargs = self._get_db_kwargs()
@ -90,7 +106,8 @@ class RDSResponse(BaseResponse):
def create_dbsecurity_group(self):
group_name = self._get_param('DBSecurityGroupName')
description = self._get_param('DBSecurityGroupDescription')
security_group = self.backend.create_security_group(group_name, description)
tags = self.unpack_complex_list_params('Tags.Tag', ('Key', 'Value'))
security_group = self.backend.create_security_group(group_name, description, tags)
template = self.response_template(CREATE_SECURITY_GROUP_TEMPLATE)
return template.render(security_group=security_group)
@ -118,7 +135,8 @@ class RDSResponse(BaseResponse):
description = self._get_param('DBSubnetGroupDescription')
subnet_ids = self._get_multi_param('SubnetIds.member')
subnets = [ec2_backends[self.region].get_subnet(subnet_id) for subnet_id in subnet_ids]
subnet_group = self.backend.create_subnet_group(subnet_name, description, subnets)
tags = self.unpack_complex_list_params('Tags.Tag', ('Key', 'Value'))
subnet_group = self.backend.create_subnet_group(subnet_name, description, subnets, tags)
template = self.response_template(CREATE_SUBNET_GROUP_TEMPLATE)
return template.render(subnet_group=subnet_group)

View File

@ -1,20 +1,22 @@
from __future__ import unicode_literals
import json
from jinja2 import Template
from werkzeug.exceptions import BadRequest
class RDSClientError(BadRequest):
def __init__(self, code, message):
super(RDSClientError, self).__init__()
self.description = json.dumps({
"Error": {
"Code": code,
"Message": message,
'Type': 'Sender',
},
'RequestId': '6876f774-7273-11e4-85dc-39e55ca848d1',
})
template = Template("""
<RDSClientError>
<Error>
<Code>{{ code }}</Code>
<Message>{{ message }}</Message>
<Type>Sender</Type>
</Error>
<RequestId>6876f774-7273-11e4-85dc-39e55ca848d1</RequestId>
</RDSClientError>""")
self.description = template.render(code=code, message=message)
class DBInstanceNotFoundError(RDSClientError):
@ -37,3 +39,8 @@ class DBSubnetGroupNotFoundError(RDSClientError):
'DBSubnetGroupNotFound',
"Subnet Group {0} not found.".format(subnet_group_name))
class DBParameterGroupNotFoundError(RDSClientError):
def __init__(self, db_parameter_group_name):
super(DBParameterGroupNotFoundError, self).__init__(
'DBParameterGroupNotFound',
'DB Parameter Group {0} not found.'.format(db_parameter_group_name))

File diff suppressed because one or more lines are too long

View File

@ -1,8 +1,10 @@
from __future__ import unicode_literals
from collections import defaultdict
from moto.core.responses import BaseResponse
from moto.ec2.models import ec2_backends
from .models import rds2_backends
from .exceptions import DBParameterGroupNotFoundError
import json
import re
@ -22,11 +24,12 @@ class RDS2Response(BaseResponse):
"db_instance_class": self._get_param('DBInstanceClass'),
"db_instance_identifier": self._get_param('DBInstanceIdentifier'),
"db_name": self._get_param("DBName"),
# DBParameterGroupName
"db_parameter_group_name": self._get_param("DBParameterGroupName"),
"db_subnet_group_name": self._get_param("DBSubnetGroupName"),
"engine": self._get_param("Engine"),
"engine_version": self._get_param("EngineVersion"),
"iops": self._get_int_param("Iops"),
"kms_key_id": self._get_param("KmsKeyId"),
"master_user_password": self._get_param('MasterUserPassword'),
"master_username": self._get_param('MasterUsername'),
"multi_az": self._get_bool_param("MultiAZ"),
@ -36,12 +39,13 @@ class RDS2Response(BaseResponse):
# PreferredMaintenanceWindow
"publicly_accessible": self._get_param("PubliclyAccessible"),
"region": self.region,
"security_groups": self._get_multi_param('DBSecurityGroups.member'),
"security_groups": self._get_multi_param('DBSecurityGroups.DBSecurityGroupName'),
"storage_encrypted": self._get_param("StorageEncrypted"),
"storage_type": self._get_param("StorageType"),
# VpcSecurityGroupIds.member.N
"tags": list()
"tags": list(),
}
args['tags'] = self.unpack_complex_list_params('Tags.member', ('Key', 'Value'))
args['tags'] = self.unpack_complex_list_params('Tags.Tag', ('Key', 'Value'))
return args
def _get_db_replica_kwargs(self):
@ -67,6 +71,14 @@ class RDS2Response(BaseResponse):
'name': self._get_param('OptionGroupName')
}
def _get_db_parameter_group_kwargs(self):
return {
'description': self._get_param('Description'),
'family': self._get_param('DBParameterGroupFamily'),
'name': self._get_param('DBParameterGroupName'),
'tags': self.unpack_complex_list_params('Tags.Tag', ('Key', 'Value')),
}
def unpack_complex_list_params(self, label, names):
unpacked_list = list()
count = 1
@ -150,7 +162,7 @@ class RDS2Response(BaseResponse):
def add_tags_to_resource(self):
arn = self._get_param('ResourceName')
tags = self.unpack_complex_list_params('Tags.member', ('Key', 'Value'))
tags = self.unpack_complex_list_params('Tags.Tag', ('Key', 'Value'))
tags = self.backend.add_tags_to_resource(arn, tags)
template = self.response_template(ADD_TAGS_TO_RESOURCE_TEMPLATE)
return template.render(tags=tags)
@ -168,7 +180,8 @@ class RDS2Response(BaseResponse):
def create_db_security_group(self):
group_name = self._get_param('DBSecurityGroupName')
description = self._get_param('DBSecurityGroupDescription')
security_group = self.backend.create_security_group(group_name, description)
tags = self.unpack_complex_list_params('Tags.Tag', ('Key', 'Value'))
security_group = self.backend.create_security_group(group_name, description, tags)
template = self.response_template(CREATE_SECURITY_GROUP_TEMPLATE)
return template.render(security_group=security_group)
@ -206,9 +219,10 @@ class RDS2Response(BaseResponse):
def create_db_subnet_group(self):
subnet_name = self._get_param('DBSubnetGroupName')
description = self._get_param('DBSubnetGroupDescription')
subnet_ids = self._get_multi_param('SubnetIds.member')
subnet_ids = self._get_multi_param('SubnetIds.SubnetIdentifier')
tags = self.unpack_complex_list_params('Tags.Tag', ('Key', 'Value'))
subnets = [ec2_backends[self.region].get_subnet(subnet_id) for subnet_id in subnet_ids]
subnet_group = self.backend.create_subnet_group(subnet_name, description, subnets)
subnet_group = self.backend.create_subnet_group(subnet_name, description, subnets, tags)
template = self.response_template(CREATE_SUBNET_GROUP_TEMPLATE)
return template.render(subnet_group=subnet_group)
@ -283,225 +297,326 @@ class RDS2Response(BaseResponse):
template = self.response_template(MODIFY_OPTION_GROUP_TEMPLATE)
return template.render(option_group=option_group)
def create_dbparameter_group(self):
return self.create_db_parameter_group()
CREATE_DATABASE_TEMPLATE = """{
"CreateDBInstanceResponse": {
"CreateDBInstanceResult": {
"DBInstance": {{ database.to_json() }}
},
"ResponseMetadata": { "RequestId": "523e3218-afc7-11c3-90f5-f90431260ab4" }
}
}"""
def create_db_parameter_group(self):
kwargs = self._get_db_parameter_group_kwargs()
db_parameter_group = self.backend.create_db_parameter_group(kwargs)
template = self.response_template(CREATE_DB_PARAMETER_GROUP_TEMPLATE)
return template.render(db_parameter_group=db_parameter_group)
CREATE_DATABASE_REPLICA_TEMPLATE = """{"CreateDBInstanceReadReplicaResponse": {
"ResponseMetadata": {
"RequestId": "5e60c46d-a844-11e4-bb68-17f36418e58f"
},
"CreateDBInstanceReadReplicaResult": {
"DBInstance": {{ database.to_json() }}
}
}}"""
def describe_dbparameter_groups(self):
return self.describe_db_parameter_groups()
DESCRIBE_DATABASES_TEMPLATE = """{
"DescribeDBInstancesResponse": {
"DescribeDBInstancesResult": {
"DBInstances": [
def describe_db_parameter_groups(self):
kwargs = self._get_db_parameter_group_kwargs()
kwargs['max_records'] = self._get_param('MaxRecords')
kwargs['marker'] = self._get_param('Marker')
db_parameter_groups = self.backend.describe_db_parameter_groups(kwargs)
template = self.response_template(DESCRIBE_DB_PARAMETER_GROUPS_TEMPLATE)
return template.render(db_parameter_groups=db_parameter_groups)
def modify_dbparameter_group(self):
return self.modify_db_parameter_group()
def modify_db_parameter_group(self):
db_parameter_group_name = self._get_param('DBParameterGroupName')
db_parameter_group_parameters = self._get_db_parameter_group_paramters()
db_parameter_group = self.backend.modify_db_parameter_group(db_parameter_group_name,
db_parameter_group_parameters)
template = self.response_template(MODIFY_DB_PARAMETER_GROUP_TEMPLATE)
return template.render(db_parameter_group=db_parameter_group)
def _get_db_parameter_group_paramters(self):
parameter_group_parameters = defaultdict(dict)
for param_name, value in self.querystring.items():
if not param_name.startswith('Parameters.Parameter'):
continue
split_param_name = param_name.split('.')
param_id = split_param_name[2]
param_setting = split_param_name[3]
parameter_group_parameters[param_id][param_setting] = value[0]
return parameter_group_parameters.values()
def describe_dbparameters(self):
return self.describe_db_parameters()
def describe_db_parameters(self):
db_parameter_group_name = self._get_param('DBParameterGroupName')
db_parameter_groups = self.backend.describe_db_parameter_groups({'name': db_parameter_group_name})
if not db_parameter_groups:
raise DBParameterGroupNotFoundError(db_parameter_group_name)
template = self.response_template(DESCRIBE_DB_PARAMETERS_TEMPLATE)
return template.render(db_parameter_group=db_parameter_groups[0])
def delete_dbparameter_group(self):
return self.delete_db_parameter_group()
def delete_db_parameter_group(self):
kwargs = self._get_db_parameter_group_kwargs()
db_parameter_group = self.backend.delete_db_parameter_group(kwargs['name'])
template = self.response_template(DELETE_DB_PARAMETER_GROUP_TEMPLATE)
return template.render(db_parameter_group=db_parameter_group)
CREATE_DATABASE_TEMPLATE = """<CreateDBInstanceResponse xmlns="http://rds.amazonaws.com/doc/2014-09-01/">
<CreateDBInstanceResult>
{{ database.to_xml() }}
</CreateDBInstanceResult>
<ResponseMetadata>
<RequestId>523e3218-afc7-11c3-90f5-f90431260ab4</RequestId>
</ResponseMetadata>
</CreateDBInstanceResponse>"""
CREATE_DATABASE_REPLICA_TEMPLATE = """<CreateDBInstanceReadReplicaResponse xmlns="http://rds.amazonaws.com/doc/2014-09-01/">
<CreateDBInstanceReadReplicaResult>
{{ database.to_xml() }}
</CreateDBInstanceReadReplicaResult>
<ResponseMetadata>
<RequestId>5e60c46d-a844-11e4-bb68-17f36418e58f</RequestId>
</ResponseMetadata>
</CreateDBInstanceReadReplicaResponse>"""
DESCRIBE_DATABASES_TEMPLATE = """<DescribeDBInstancesResponse xmlns="http://rds.amazonaws.com/doc/2014-09-01/">
<DescribeDBInstancesResult>
<DBInstances>
{%- for database in databases -%}
{%- if loop.index != 1 -%},{%- endif -%}
{{ database.to_json() }}
{{ database.to_xml() }}
{%- endfor -%}
]
},
"ResponseMetadata": { "RequestId": "523e3218-afc7-11c3-90f5-f90431260ab4" }
}
}"""
</DBInstances>
</DescribeDBInstancesResult>
<ResponseMetadata>
<RequestId>523e3218-afc7-11c3-90f5-f90431260ab4</RequestId>
</ResponseMetadata>
</DescribeDBInstancesResponse>"""
MODIFY_DATABASE_TEMPLATE = """{"ModifyDBInstanceResponse": {
"ModifyDBInstanceResult": {
"DBInstance": {{ database.to_json() }},
"ResponseMetadata": {
"RequestId": "bb58476c-a1a8-11e4-99cf-55e92d4bbada"
}
}
}
}"""
MODIFY_DATABASE_TEMPLATE = """<ModifyDBInstanceResponse xmlns="http://rds.amazonaws.com/doc/2014-09-01/">
<ModifyDBInstanceResult>
{{ database.to_xml() }}
</ModifyDBInstanceResult>
<ResponseMetadata>
<RequestId>bb58476c-a1a8-11e4-99cf-55e92d4bbada</RequestId>
</ResponseMetadata>
</ModifyDBInstanceResponse>"""
REBOOT_DATABASE_TEMPLATE = """{"RebootDBInstanceResponse": {
"RebootDBInstanceResult": {
"DBInstance": {{ database.to_json() }},
"ResponseMetadata": {
"RequestId": "d55711cb-a1ab-11e4-99cf-55e92d4bbada"
}
}
}
}"""
REBOOT_DATABASE_TEMPLATE = """<RebootDBInstanceResponse xmlns="http://rds.amazonaws.com/doc/2014-09-01/">
<RebootDBInstanceResult>
{{ database.to_xml() }}
</RebootDBInstanceResult>
<ResponseMetadata>
<RequestId>d55711cb-a1ab-11e4-99cf-55e92d4bbada</RequestId>
</ResponseMetadata>
</RebootDBInstanceResponse>"""
DELETE_DATABASE_TEMPLATE = """{ "DeleteDBInstanceResponse": {
"DeleteDBInstanceResult": {
"DBInstance": {{ database.to_json() }}
},
"ResponseMetadata": {
"RequestId": "523e3218-afc7-11c3-90f5-f90431260ab4"
}
}
}"""
DELETE_DATABASE_TEMPLATE = """<DeleteDBInstanceResponse xmlns="http://rds.amazonaws.com/doc/2014-09-01/">
<DeleteDBInstanceResult>
{{ database.to_xml() }}
</DeleteDBInstanceResult>
<ResponseMetadata>
<RequestId>7369556f-b70d-11c3-faca-6ba18376ea1b</RequestId>
</ResponseMetadata>
</DeleteDBInstanceResponse>"""
CREATE_SECURITY_GROUP_TEMPLATE = """{"CreateDBSecurityGroupResponse": {
"CreateDBSecurityGroupResult": {
"DBSecurityGroup":
{{ security_group.to_json() }},
"ResponseMetadata": {
"RequestId": "462165d0-a77a-11e4-a5fa-75b30c556f97"
}}
}
}"""
CREATE_SECURITY_GROUP_TEMPLATE = """<CreateDBSecurityGroupResponse xmlns="http://rds.amazonaws.com/doc/2014-09-01/">
<CreateDBSecurityGroupResult>
{{ security_group.to_xml() }}
</CreateDBSecurityGroupResult>
<ResponseMetadata>
<RequestId>462165d0-a77a-11e4-a5fa-75b30c556f97</RequestId>
</ResponseMetadata>
</CreateDBSecurityGroupResponse>"""
DESCRIBE_SECURITY_GROUPS_TEMPLATE = """{
"DescribeDBSecurityGroupsResponse": {
"ResponseMetadata": {
"RequestId": "5df2014e-a779-11e4-bdb0-594def064d0c"
},
"DescribeDBSecurityGroupsResult": {
"Marker": "null",
"DBSecurityGroups": [
DESCRIBE_SECURITY_GROUPS_TEMPLATE = """<DescribeDBSecurityGroupsResponse xmlns="http://rds.amazonaws.com/doc/2014-09-01/">
<DescribeDBSecurityGroupsResult>
<DBSecurityGroups>
{% for security_group in security_groups %}
{%- if loop.index != 1 -%},{%- endif -%}
{{ security_group.to_json() }}
{{ security_group.to_xml() }}
{% endfor %}
]
}
}
}"""
</DBSecurityGroups>
</DescribeDBSecurityGroupsResult>
<ResponseMetadata>
<RequestId>5df2014e-a779-11e4-bdb0-594def064d0c</RequestId>
</ResponseMetadata>
</DescribeDBSecurityGroupsResponse>"""
DELETE_SECURITY_GROUP_TEMPLATE = """{"DeleteDBSecurityGroupResponse": {
"ResponseMetadata": {
"RequestId": "97e846bd-a77d-11e4-ac58-91351c0f3426"
}
}}"""
DELETE_SECURITY_GROUP_TEMPLATE = """<DeleteDBSecurityGroupResponse xmlns="http://rds.amazonaws.com/doc/2014-09-01/">
<ResponseMetadata>
<RequestId>97e846bd-a77d-11e4-ac58-91351c0f3426</RequestId>
</ResponseMetadata>
</DeleteDBSecurityGroupResponse>"""
AUTHORIZE_SECURITY_GROUP_TEMPLATE = """{
"AuthorizeDBSecurityGroupIngressResponse": {
"AuthorizeDBSecurityGroupIngressResult": {
"DBSecurityGroup": {{ security_group.to_json() }}
},
"ResponseMetadata": {
"RequestId": "75d32fd5-a77e-11e4-8892-b10432f7a87d"
}
}
}"""
AUTHORIZE_SECURITY_GROUP_TEMPLATE = """<AuthorizeDBSecurityGroupIngressResponse xmlns="http://rds.amazonaws.com/doc/2014-09-01/">
<AuthorizeDBSecurityGroupIngressResult>
{{ security_group.to_xml() }}
</AuthorizeDBSecurityGroupIngressResult>
<ResponseMetadata>
<RequestId>75d32fd5-a77e-11e4-8892-b10432f7a87d</RequestId>
</ResponseMetadata>
</AuthorizeDBSecurityGroupIngressResponse>"""
CREATE_SUBNET_GROUP_TEMPLATE = """{
"CreateDBSubnetGroupResponse": {
"CreateDBSubnetGroupResult":
{ {{ subnet_group.to_json() }} },
"ResponseMetadata": { "RequestId": "3a401b3f-bb9e-11d3-f4c6-37db295f7674" }
}
}"""
CREATE_SUBNET_GROUP_TEMPLATE = """<CreateDBSubnetGroupResponse xmlns="http://rds.amazonaws.com/doc/2014-09-01/">
<CreateDBSubnetGroupResult>
{{ subnet_group.to_xml() }}
</CreateDBSubnetGroupResult>
<ResponseMetadata>
<RequestId>3a401b3f-bb9e-11d3-f4c6-37db295f7674</RequestId>
</ResponseMetadata>
</CreateDBSubnetGroupResponse>"""
DESCRIBE_SUBNET_GROUPS_TEMPLATE = """{
"DescribeDBSubnetGroupsResponse": {
"DescribeDBSubnetGroupsResult": {
"DBSubnetGroups": [
DESCRIBE_SUBNET_GROUPS_TEMPLATE = """<DescribeDBSubnetGroupsResponse xmlns="http://rds.amazonaws.com/doc/2014-09-01/">
<DescribeDBSubnetGroupsResult>
<DBSubnetGroups>
{% for subnet_group in subnet_groups %}
{ {{ subnet_group.to_json() }} }{%- if not loop.last -%},{%- endif -%}
{{ subnet_group.to_xml() }}
{% endfor %}
],
"Marker": null
},
"ResponseMetadata": { "RequestId": "b783db3b-b98c-11d3-fbc7-5c0aad74da7c" }
}
}"""
</DBSubnetGroups>
</DescribeDBSubnetGroupsResult>
<ResponseMetadata>
<RequestId>b783db3b-b98c-11d3-fbc7-5c0aad74da7c</RequestId>
</ResponseMetadata>
</DescribeDBSubnetGroupsResponse>"""
DELETE_SUBNET_GROUP_TEMPLATE = """<DeleteDBSubnetGroupResponse xmlns="http://rds.amazonaws.com/doc/2014-09-01/">
<ResponseMetadata>
<RequestId>13785dd5-a7fc-11e4-bb9c-7f371d0859b0</RequestId>
</ResponseMetadata>
</DeleteDBSubnetGroupResponse>"""
DELETE_SUBNET_GROUP_TEMPLATE = """{"DeleteDBSubnetGroupResponse": {"ResponseMetadata": {"RequestId": "13785dd5-a7fc-11e4-bb9c-7f371d0859b0"}}}"""
CREATE_OPTION_GROUP_TEMPLATE = """<CreateOptionGroupResponse xmlns="http://rds.amazonaws.com/doc/2014-09-01/">
<CreateOptionGroupResult>
{{ option_group.to_xml() }}
</CreateOptionGroupResult>
<ResponseMetadata>
<RequestId>1e38dad4-9f50-11e4-87ea-a31c60ed2e36</RequestId>
</ResponseMetadata>
</CreateOptionGroupResponse>"""
CREATE_OPTION_GROUP_TEMPLATE = """{
"CreateOptionGroupResponse": {
"CreateOptionGroupResult": {
"OptionGroup": {{ option_group.to_json() }}
},
"ResponseMetadata": {
"RequestId": "1e38dad4-9f50-11e4-87ea-a31c60ed2e36"
}
}
}"""
DELETE_OPTION_GROUP_TEMPLATE = """<DeleteOptionGroupResponse xmlns="http://rds.amazonaws.com/doc/2014-09-01/">
<ResponseMetadata>
<RequestId>e2590367-9fa2-11e4-99cf-55e92d41c60e</RequestId>
</ResponseMetadata>
</DeleteOptionGroupResponse>"""
DELETE_OPTION_GROUP_TEMPLATE = \
"""{"DeleteOptionGroupResponse": {"ResponseMetadata": {"RequestId": "e2590367-9fa2-11e4-99cf-55e92d41c60e"}}}"""
DESCRIBE_OPTION_GROUP_TEMPLATE = \
"""{"DescribeOptionGroupsResponse": {
"DescribeOptionGroupsResult": {
"Marker": null,
"OptionGroupsList": [
DESCRIBE_OPTION_GROUP_TEMPLATE = """<DescribeOptionGroupsResponse xmlns="http://rds.amazonaws.com/doc/2014-09-01/">
<DescribeOptionGroupsResult>
<OptionGroupsList>
{%- for option_group in option_groups -%}
{%- if loop.index != 1 -%},{%- endif -%}
{{ option_group.to_json() }}
{{ option_group.to_xml() }}
{%- endfor -%}
]},
"ResponseMetadata": {"RequestId": "4caf445d-9fbc-11e4-87ea-a31c60ed2e36"}
}}"""
</OptionGroupsList>
</DescribeOptionGroupsResult>
<ResponseMetadata>
<RequestId>4caf445d-9fbc-11e4-87ea-a31c60ed2e36</RequestId>
</ResponseMetadata>
</DescribeOptionGroupsResponse>"""
DESCRIBE_OPTION_GROUP_OPTIONS_TEMPLATE = \
"""{"DescribeOptionGroupOptionsResponse": {
"DescribeOptionGroupOptionsResult": {
"Marker": null,
"OptionGroupOptions": [
DESCRIBE_OPTION_GROUP_OPTIONS_TEMPLATE = """<DescribeOptionGroupOptionsResponse xmlns="http://rds.amazonaws.com/doc/2014-09-01/">
<DescribeOptionGroupOptionsResult>
<OptionGroupOptions>
{%- for option_group_option in option_group_options -%}
{%- if loop.index != 1 -%},{%- endif -%}
{{ option_group_option.to_json() }}
{{ option_group_option.to_xml() }}
{%- endfor -%}
]},
"ResponseMetadata": {"RequestId": "457f7bb8-9fbf-11e4-9084-5754f80d5144"}
}}"""
</OptionGroupOptions>
</DescribeOptionGroupOptionsResult>
<ResponseMetadata>
<RequestId>457f7bb8-9fbf-11e4-9084-5754f80d5144</RequestId>
</ResponseMetadata>
</DescribeOptionGroupOptionsResponse>"""
MODIFY_OPTION_GROUP_TEMPLATE = \
"""{"ModifyOptionGroupResponse": {
"ResponseMetadata": {
"RequestId": "ce9284a5-a0de-11e4-b984-a11a53e1f328"
},
"ModifyOptionGroupResult":
{{ option_group.to_json() }}
}
}"""
MODIFY_OPTION_GROUP_TEMPLATE = """<ModifyOptionGroupResponse xmlns="http://rds.amazonaws.com/doc/2014-09-01/">
<ModifyOptionGroupResult>
{{ option_group.to_xml() }}
</ModifyOptionGroupResult>
<ResponseMetadata>
<RequestId>ce9284a5-a0de-11e4-b984-a11a53e1f328</RequestId>
</ResponseMetadata>
</ModifyOptionGroupResponse>"""
LIST_TAGS_FOR_RESOURCE_TEMPLATE = \
"""{"ListTagsForResourceResponse":
{"ListTagsForResourceResult":
{"TagList": [
CREATE_DB_PARAMETER_GROUP_TEMPLATE = """<CreateDBParameterGroupResponse xmlns="http://rds.amazonaws.com/doc/2014-09-01/">
<CreateDBParameterGroupResult>
{{ db_parameter_group.to_xml() }}
</CreateDBParameterGroupResult>
<ResponseMetadata>
<RequestId>7805c127-af22-11c3-96ac-6999cc5f7e72</RequestId>
</ResponseMetadata>
</CreateDBParameterGroupResponse>"""
DESCRIBE_DB_PARAMETER_GROUPS_TEMPLATE = """<DescribeDBParameterGroupsResponse xmlns="http://rds.amazonaws.com/doc/2014-09-01/">
<DescribeDBParameterGroupsResult>
<DBParameterGroups>
{%- for db_parameter_group in db_parameter_groups -%}
{{ db_parameter_group.to_xml() }}
{%- endfor -%}
</DBParameterGroups>
</DescribeDBParameterGroupsResult>
<ResponseMetadata>
<RequestId>b75d527a-b98c-11d3-f272-7cd6cce12cc5</RequestId>
</ResponseMetadata>
</DescribeDBParameterGroupsResponse>"""
MODIFY_DB_PARAMETER_GROUP_TEMPLATE = """<ModifyDBParameterGroupResponse xmlns="http://rds.amazonaws.com/doc/2014-09-01/">
<ModifyDBParameterGroupResult>
<DBParameterGroupName>{{ db_parameter_group.name }}</DBParameterGroupName>
</ModifyDBParameterGroupResult>
<ResponseMetadata>
<RequestId>12d7435e-bba0-11d3-fe11-33d33a9bb7e3</RequestId>
</ResponseMetadata>
</ModifyDBParameterGroupResponse>"""
DELETE_DB_PARAMETER_GROUP_TEMPLATE = """<DeleteDBParameterGroupResponse xmlns="http://rds.amazonaws.com/doc/2014-09-01/">
<ResponseMetadata>
<RequestId>cad6c267-ba25-11d3-fe11-33d33a9bb7e3</RequestId>
</ResponseMetadata>
</DeleteDBParameterGroupResponse>"""
DESCRIBE_DB_PARAMETERS_TEMPLATE = """<DescribeDBParametersResponse xmlns="http://rds.amazonaws.com/doc/2014-09-01/">
<DescribeDBParametersResult>
<Parameters>
{%- for db_parameter_name, db_parameter in db_parameter_group.parameters.items() -%}
<Parameter>
{%- for parameter_name, parameter_value in db_parameter.items() -%}
<{{ parameter_name }}>{{ parameter_value }}</{{ parameter_name }}>
{%- endfor -%}
</Parameter>
{%- endfor -%}
</Parameters>
</DescribeDBParametersResult>
<ResponseMetadata>
<RequestId>8c40488f-b9ff-11d3-a15e-7ac49293f4fa</RequestId>
</ResponseMetadata>
</DescribeDBParametersResponse>
"""
LIST_TAGS_FOR_RESOURCE_TEMPLATE = """<ListTagsForResourceResponse xmlns="http://rds.amazonaws.com/doc/2014-10-31/">
<ListTagsForResourceResult>
<TagList>
{%- for tag in tags -%}
{%- if loop.index != 1 -%},{%- endif -%}
{
"Key": "{{ tag['Key'] }}",
"Value": "{{ tag['Value'] }}"
}
<Tag>
<Key>{{ tag['Key'] }}</Key>
<Value>{{ tag['Value'] }}</Value>
</Tag>
{%- endfor -%}
]},
"ResponseMetadata": {
"RequestId": "8c21ba39-a598-11e4-b688-194eaf8658fa"
}
}
}"""
</TagList>
</ListTagsForResourceResult>
<ResponseMetadata>
<RequestId>8c21ba39-a598-11e4-b688-194eaf8658fa</RequestId>
</ResponseMetadata>
</ListTagsForResourceResponse>"""
ADD_TAGS_TO_RESOURCE_TEMPLATE = \
"""{"ListTagsForResourceResponse": {
"ListTagsForResourceResult": {
"TagList": [
{%- for tag in tags -%}
{%- if loop.index != 1 -%},{%- endif -%}
{
"Key": "{{ tag['Key'] }}",
"Value": "{{ tag['Value'] }}"
}
{%- endfor -%}
]},
"ResponseMetadata": {
"RequestId": "b194d9ca-a664-11e4-b688-194eaf8658fa"
}
}
}"""
ADD_TAGS_TO_RESOURCE_TEMPLATE = """<AddTagsToResourceResponse xmlns="http://rds.amazonaws.com/doc/2014-10-31/">
<ResponseMetadata>
<RequestId>b194d9ca-a664-11e4-b688-194eaf8658fa</RequestId>
</ResponseMetadata>
</AddTagsToResourceResponse>"""
REMOVE_TAGS_FROM_RESOURCE_TEMPLATE = \
"""{"RemoveTagsFromResourceResponse": {"ResponseMetadata": {"RequestId": "c6499a01-a664-11e4-8069-fb454b71a80e"}}}
"""
REMOVE_TAGS_FROM_RESOURCE_TEMPLATE = """<RemoveTagsFromResourceResponse xmlns="http://rds.amazonaws.com/doc/2014-10-31/">
<ResponseMetadata>
<RequestId>b194d9ca-a664-11e4-b688-194eaf8658fa</RequestId>
</ResponseMetadata>
</RemoveTagsFromResourceResponse>"""

View File

@ -0,0 +1,201 @@
from __future__ import unicode_literals
template = {
"AWSTemplateFormatVersion" : "2010-09-09",
"Description" : "AWS CloudFormation Sample Template RDS_MySQL_With_Read_Replica: Sample template showing how to create a highly-available, RDS DBInstance with a read replica. **WARNING** This template creates an Amazon Relational Database Service database instance and Amazon CloudWatch alarms. You will be billed for the AWS resources used if you create a stack from this template.",
"Parameters": {
"DBName": {
"Default": "MyDatabase",
"Description" : "The database name",
"Type": "String",
"MinLength": "1",
"MaxLength": "64",
"AllowedPattern" : "[a-zA-Z][a-zA-Z0-9]*",
"ConstraintDescription" : "must begin with a letter and contain only alphanumeric characters."
},
"DBInstanceIdentifier": {
"Type": "String"
},
"DBUser": {
"NoEcho": "true",
"Description" : "The database admin account username",
"Type": "String",
"MinLength": "1",
"MaxLength": "16",
"AllowedPattern" : "[a-zA-Z][a-zA-Z0-9]*",
"ConstraintDescription" : "must begin with a letter and contain only alphanumeric characters."
},
"DBPassword": {
"NoEcho": "true",
"Description" : "The database admin account password",
"Type": "String",
"MinLength": "1",
"MaxLength": "41",
"AllowedPattern" : "[a-zA-Z0-9]+",
"ConstraintDescription" : "must contain only alphanumeric characters."
},
"DBAllocatedStorage": {
"Default": "5",
"Description" : "The size of the database (Gb)",
"Type": "Number",
"MinValue": "5",
"MaxValue": "1024",
"ConstraintDescription" : "must be between 5 and 1024Gb."
},
"DBInstanceClass": {
"Description" : "The database instance type",
"Type": "String",
"Default": "db.m1.small",
"AllowedValues" : [ "db.t1.micro", "db.m1.small", "db.m1.medium", "db.m1.large", "db.m1.xlarge", "db.m2.xlarge", "db.m2.2xlarge", "db.m2.4xlarge", "db.m3.medium", "db.m3.large", "db.m3.xlarge", "db.m3.2xlarge", "db.r3.large", "db.r3.xlarge", "db.r3.2xlarge", "db.r3.4xlarge", "db.r3.8xlarge", "db.m2.xlarge", "db.m2.2xlarge", "db.m2.4xlarge", "db.cr1.8xlarge"]
,
"ConstraintDescription" : "must select a valid database instance type."
},
"EC2SecurityGroup": {
"Description" : "The EC2 security group that contains instances that need access to the database",
"Default": "default",
"Type": "String",
"AllowedPattern" : "[a-zA-Z0-9\\-]+",
"ConstraintDescription" : "must be a valid security group name."
},
"MultiAZ" : {
"Description" : "Multi-AZ master database",
"Type" : "String",
"Default" : "false",
"AllowedValues" : [ "true", "false" ],
"ConstraintDescription" : "must be true or false."
}
},
"Conditions" : {
"Is-EC2-VPC" : { "Fn::Or" : [ {"Fn::Equals" : [{"Ref" : "AWS::Region"}, "eu-central-1" ]},
{"Fn::Equals" : [{"Ref" : "AWS::Region"}, "cn-north-1" ]}]},
"Is-EC2-Classic" : { "Fn::Not" : [{ "Condition" : "Is-EC2-VPC"}]}
},
"Resources" : {
"DBParameterGroup": {
"Type": "AWS::RDS::DBParameterGroup",
"Properties" : {
"Description": "DB Parameter Goup",
"Family" : "MySQL5.1",
"Parameters": {
"BACKLOG_QUEUE_LIMIT": "2048"
}
}
},
"DBEC2SecurityGroup": {
"Type": "AWS::EC2::SecurityGroup",
"Condition" : "Is-EC2-VPC",
"Properties" : {
"GroupDescription": "Open database for access",
"SecurityGroupIngress" : [{
"IpProtocol" : "tcp",
"FromPort" : "3306",
"ToPort" : "3306",
"SourceSecurityGroupName" : { "Ref" : "EC2SecurityGroup" }
}]
}
},
"DBSecurityGroup": {
"Type": "AWS::RDS::DBSecurityGroup",
"Condition" : "Is-EC2-Classic",
"Properties": {
"DBSecurityGroupIngress": [{
"EC2SecurityGroupName": { "Ref": "EC2SecurityGroup" }
}],
"GroupDescription": "database access"
}
},
"my_vpc": {
"Type" : "AWS::EC2::VPC",
"Properties" : {
"CidrBlock" : "10.0.0.0/16",
}
},
"EC2Subnet": {
"Type" : "AWS::EC2::Subnet",
"Condition" : "Is-EC2-VPC",
"Properties" : {
"AvailabilityZone" : "eu-central-1a",
"CidrBlock" : "10.0.1.0/24",
"VpcId" : { "Ref" : "my_vpc" }
}
},
"DBSubnet": {
"Type": "AWS::RDS::DBSubnetGroup",
"Condition" : "Is-EC2-VPC",
"Properties": {
"DBSubnetGroupDescription": "my db subnet group",
"SubnetIds" : [ { "Ref": "EC2Subnet" } ],
}
},
"MasterDB" : {
"Type" : "AWS::RDS::DBInstance",
"Properties" : {
"DBInstanceIdentifier": { "Ref": "DBInstanceIdentifier" },
"DBName" : { "Ref" : "DBName" },
"AllocatedStorage" : { "Ref" : "DBAllocatedStorage" },
"DBInstanceClass" : { "Ref" : "DBInstanceClass" },
"Engine" : "MySQL",
"DBSubnetGroupName": {"Fn::If": ["Is-EC2-VPC", { "Ref": "DBSubnet" }, { "Ref": "AWS::NoValue" }]},
"MasterUsername" : { "Ref" : "DBUser" },
"MasterUserPassword" : { "Ref" : "DBPassword" },
"MultiAZ" : { "Ref" : "MultiAZ" },
"Tags" : [{ "Key" : "Name", "Value" : "Master Database" }],
"VPCSecurityGroups": { "Fn::If" : [ "Is-EC2-VPC", [ { "Fn::GetAtt": [ "DBEC2SecurityGroup", "GroupId" ] } ], { "Ref" : "AWS::NoValue"}]},
"DBSecurityGroups": { "Fn::If" : [ "Is-EC2-Classic", [ { "Ref": "DBSecurityGroup" } ], { "Ref" : "AWS::NoValue"}]}
},
"DeletionPolicy" : "Snapshot"
},
"ReplicaDB" : {
"Type" : "AWS::RDS::DBInstance",
"Properties" : {
"SourceDBInstanceIdentifier" : { "Ref" : "MasterDB" },
"DBInstanceClass" : { "Ref" : "DBInstanceClass" },
"Tags" : [{ "Key" : "Name", "Value" : "Read Replica Database" }]
}
}
},
"Outputs" : {
"EC2Platform" : {
"Description" : "Platform in which this stack is deployed",
"Value" : { "Fn::If" : [ "Is-EC2-VPC", "EC2-VPC", "EC2-Classic" ]}
},
"MasterJDBCConnectionString": {
"Description" : "JDBC connection string for the master database",
"Value" : { "Fn::Join": [ "", [ "jdbc:mysql://",
{ "Fn::GetAtt": [ "MasterDB", "Endpoint.Address" ] },
":",
{ "Fn::GetAtt": [ "MasterDB", "Endpoint.Port" ] },
"/",
{ "Ref": "DBName" }]]}
},
"ReplicaJDBCConnectionString": {
"Description" : "JDBC connection string for the replica database",
"Value" : { "Fn::Join": [ "", [ "jdbc:mysql://",
{ "Fn::GetAtt": [ "ReplicaDB", "Endpoint.Address" ] },
":",
{ "Fn::GetAtt": [ "ReplicaDB", "Endpoint.Port" ] },
"/",
{ "Ref": "DBName" }]]}
}
}
}

View File

@ -82,7 +82,6 @@ template = {
},
"Resources" : {
"DBEC2SecurityGroup": {
"Type": "AWS::EC2::SecurityGroup",
"Condition" : "Is-EC2-VPC",
@ -101,9 +100,9 @@ template = {
"Type": "AWS::RDS::DBSecurityGroup",
"Condition" : "Is-EC2-Classic",
"Properties": {
"DBSecurityGroupIngress": {
"DBSecurityGroupIngress": [{
"EC2SecurityGroupName": { "Ref": "EC2SecurityGroup" }
},
}],
"GroupDescription": "database access"
}
},

View File

@ -27,6 +27,7 @@ from moto import (
mock_kms,
mock_lambda,
mock_rds,
mock_rds2,
mock_redshift,
mock_route53,
mock_sns,
@ -36,6 +37,7 @@ from moto import (
from .fixtures import (
ec2_classic_eip,
fn_join,
rds_mysql_with_db_parameter_group,
rds_mysql_with_read_replica,
redshift,
route53_ec2_instance_with_public_ip,
@ -693,6 +695,44 @@ def test_vpc_single_instance_in_subnet():
eip_resource = [resource for resource in resources if resource.resource_type == 'AWS::EC2::EIP'][0]
eip_resource.physical_resource_id.should.equal(eip.allocation_id)
@mock_cloudformation()
@mock_ec2()
@mock_rds2()
def test_rds_db_parameter_groups():
ec2_conn = boto.ec2.connect_to_region("us-west-1")
ec2_conn.create_security_group('application', 'Our Application Group')
template_json = json.dumps(rds_mysql_with_db_parameter_group.template)
conn = boto.cloudformation.connect_to_region("us-west-1")
conn.create_stack(
"test_stack",
template_body=template_json,
parameters=[
("DBInstanceIdentifier", "master_db"),
("DBName", "my_db"),
("DBUser", "my_user"),
("DBPassword", "my_password"),
("DBAllocatedStorage", "20"),
("DBInstanceClass", "db.m1.medium"),
("EC2SecurityGroup", "application"),
("MultiAZ", "true"),
],
)
rds_conn = boto3.client('rds', region_name="us-west-1")
db_parameter_groups = rds_conn.describe_db_parameter_groups()
len(db_parameter_groups['DBParameterGroups']).should.equal(1)
db_parameter_group_name = db_parameter_groups['DBParameterGroups'][0]['DBParameterGroupName']
found_cloudformation_set_parameter = False
for db_parameter in rds_conn.describe_db_parameters(DBParameterGroupName=db_parameter_group_name)['Parameters']:
if db_parameter['ParameterName'] == 'BACKLOG_QUEUE_LIMIT' and db_parameter['ParameterValue'] == '2048':
found_cloudformation_set_parameter = True
found_cloudformation_set_parameter.should.equal(True)
@mock_cloudformation()
@mock_ec2()

View File

@ -238,6 +238,32 @@ def test_create_database_replica():
primary = conn.get_all_dbinstances("db-master-1")[0]
list(primary.read_replica_dbinstance_identifiers).should.have.length_of(0)
@disable_on_py3()
@mock_rds
def test_create_cross_region_database_replica():
west_1_conn = boto.rds.connect_to_region("us-west-1")
west_2_conn = boto.rds.connect_to_region("us-west-2")
primary = west_1_conn.create_dbinstance("db-master-1", 10, 'db.m1.small', 'root', 'hunter2')
primary_arn = "arn:aws:rds:us-west-1:1234567890:db:db-master-1"
replica = west_2_conn.create_dbinstance_read_replica(
"replica",
primary_arn,
"db.m1.small",
)
primary = west_1_conn.get_all_dbinstances("db-master-1")[0]
primary.read_replica_dbinstance_identifiers[0].should.equal("replica")
replica = west_2_conn.get_all_dbinstances("replica")[0]
replica.instance_class.should.equal("db.m1.small")
west_2_conn.delete_dbinstance("replica")
primary = west_1_conn.get_all_dbinstances("db-master-1")[0]
list(primary.read_replica_dbinstance_identifiers).should.have.length_of(0)
@disable_on_py3()
@mock_rds

File diff suppressed because it is too large Load Diff