Repository: aws-cloudformation/cfn-lint

Issue:
Remove try... convert of integers (E3002)
In the property Type check, when the property value should be an integer (but it's not), there is an attempt to convert it:

https://github.com/awslabs/cfn-python-lint/blob/master/src/cfnlint/rules/resources/properties/Properties.py#L56

Since both YAML and JSON just handle these types out the box, the `try..except` can in theory be removed (since the type's don't match, a string is not an integer).

Personally I like the check to be more "strict', who do other think?  

Example (JSON): 
https://github.com/awslabs/cfn-python-lint/blob/master/test/templates/quickstart/vpc-management.json#L816

Example (YAML):
https://github.com/awslabs/cfn-python-lint/blob/master/test/templates/good/resource_properties.yaml#L464

These values can easily be written as actual integers
```
      - CidrIp:
          Ref: SSHLocation
        FromPort: 22 
        IpProtocol: tcp
        ToPort: 22
```


Discussion:
To make sure I understand completely.  You want numbers to not be strings at all?  I started that way but after hitting a few quickstarts I noticed a large number of examples of people still using strings so I added that check. 

@cmmeyer what do you think?  This gets into this tricky space as the template would work but wouldn't follow best practices.  This seems like something that should be a Warning not an Error.  
Yup, that. We have the same in almost all out stacks: a lot of numbers specified as strings.

It's annoying but the types actually don't match, so semantically it's actually "wrong", althought it works. 

Just a question because it works fine "as is", but don't mind it being strict.

Perhaps create a similar rule as a `warning` that actually breaks?
I actually have the reverse - an invalid integer where a String is required by CFN itself, that cfn-lint doesn't catch. The CloudFormation Designer doesn't catch it either.

In a https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-elasticbeanstalk-configurationtemplate-configurationoptionsetting.html all properties must be Strings.
For example, an entirely reasonable-looking, but wrong use of an integer:
```json
{
                        "Namespace": "aws:autoscaling:asg",
                        "OptionName": "MinSize",
                        "Value": 12
},
```
Does anyone know if the same bit of code is related, or do I need to hunt for a bug in the Specification?

@finchd and this fails when you launch to the stack?  I think it’s a similar issue to what is being described.  I think it’s a fair request by both to say that if it’s a string it’s a string and if it’s a number it’s a number and try not to translate it.   We should be capturing your issue if that stack failed to deploy because of that issue. I will need to track that down if that’s the case.  I’m not sure this is a spec issue as it looks like they use that generic property for string and number based values. 
Yes, it definitely fails to create the stack: "Value of property Value must be of type String"
`aws cloudformation validate-template` succeeds with no errors either.
Thanks. I’ll take a look at it tonight. We should be catching that 

After chatting with @kddejong tonight, I'm in the camp of strong enforcement of types.  It may trigger a lot of errors/warnings in old templates, but it's an opportunity to polish them up.  It'll be like when I first added the Flake8 linter to Atom. :)

Resolving patch:
diff --git a/src/cfnlint/helpers.py b/src/cfnlint/helpers.py
--- a/src/cfnlint/helpers.py
+++ b/src/cfnlint/helpers.py
@@ -58,6 +58,11 @@
     'cn-north-1a', 'cn-north-1b',
 ]
 
+FUNCTIONS = [
+    'Fn::Base64', 'Fn::GetAtt', 'Fn::GetAZs', 'Fn::ImportValue',
+    'Fn::Join', 'Fn::Split', 'Fn::FindInMap', 'Fn::Select', 'Ref',
+    'Fn::If', 'Fn::Contains', 'Fn::Sub', 'Fn::Cidr']
+
 LIMITS = {
     'mappings': {
         'number': 100,
diff --git a/src/cfnlint/rules/resources/properties/Properties.py b/src/cfnlint/rules/resources/properties/Properties.py
--- a/src/cfnlint/rules/resources/properties/Properties.py
+++ b/src/cfnlint/rules/resources/properties/Properties.py
@@ -34,7 +34,16 @@ def __init__(self):
         self.parameternames = {}
 
     def primitivetypecheck(self, value, primtype, proppath):
-        """Check primitive types"""
+        """
+            Check primitive types.
+            Only check that a primitive type is actual a primitive type:
+            - If its JSON let it go
+            - If its Conditions check each sub path of the condition
+            - If its a object make sure its a valid function and function
+            - If its a list raise an error
+
+        """
+
         matches = list()
         if isinstance(value, dict) and primtype == 'Json':
             return matches
@@ -55,33 +64,8 @@ def primitivetypecheck(self, value, primtype, proppath):
                                          'Fn::If', 'Fn::Contains', 'Fn::Sub', 'Fn::Cidr']:
                         message = 'Property %s has an illegal function %s' % ('/'.join(map(str, proppath)), sub_key)
                         matches.append(RuleMatch(proppath, message))
-        elif isinstance(value, str):
-            if primtype in ['Integer', 'Double', 'Long']:
-                try:
-                    int(value)
-                except ValueError:
-                    # LOGGER.error('Tried to convert %s to int. %s',
-                    #             text[prop], Exception)
-                    message = 'Property %s should be of type Int/Boolean' % ('/'.join(map(str, proppath)))
-                    matches.append(RuleMatch(proppath, message))
-            elif primtype == 'Boolean':
-                if value not in ['true', 'false', 'True', 'False']:
-                    message = 'Property %s should be of type Boolean' % ('/'.join(map(str, proppath)))
-                    matches.append(RuleMatch(proppath, message))
-            elif primtype != 'String':
-                message = 'Property %s should be of type String' % ('/'.join(map(str, proppath)))
-                matches.append(RuleMatch(proppath, message))
-        elif isinstance(value, bool):
-            if primtype != 'Boolean' and primtype != 'String':
-                message = 'Property %s should be of type %s' % ('/'.join(map(str, proppath)), primtype)
-                matches.append(RuleMatch(proppath, message))
-        elif isinstance(value, int):
-            if primtype in ['String', 'Double', 'Long']:
-                pass
-                # LOGGER.info('%s is an int but should be %s for resource %s',
-                #            text[prop], primtype, resourcename)
-            elif primtype not in ['Integer', 'Long']:
-                message = 'Property %s should be of type Integer' % ('/'.join(map(str, proppath)))
+            else:
+                message = 'Property is an object instead of %s at %s' % (primtype, '/'.join(map(str, proppath)))
                 matches.append(RuleMatch(proppath, message))
         elif isinstance(value, list):
             message = 'Property should be of type %s not List at %s' % (primtype, '/'.join(map(str, proppath)))
diff --git a/src/cfnlint/rules/resources/properties/ValuePrimitiveType.py b/src/cfnlint/rules/resources/properties/ValuePrimitiveType.py
new file mode 100644
--- /dev/null
+++ b/src/cfnlint/rules/resources/properties/ValuePrimitiveType.py
@@ -0,0 +1,114 @@
+"""
+  Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+
+  Permission is hereby granted, free of charge, to any person obtaining a copy of this
+  software and associated documentation files (the "Software"), to deal in the Software
+  without restriction, including without limitation the rights to use, copy, modify,
+  merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
+  permit persons to whom the Software is furnished to do so.
+
+  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+  INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+  PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+  SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+"""
+import sys
+import six
+from cfnlint import CloudFormationLintRule
+from cfnlint import RuleMatch
+import cfnlint.helpers
+
+
+class ValuePrimitiveType(CloudFormationLintRule):
+    """Check if Resource PrimitiveTypes are correct"""
+    id = 'E3012'
+    shortdesc = 'Check resource properties values'
+    description = 'Checks resource property values with Primitive Types for ' \
+                  'values that match those types.'
+    tags = ['base', 'resources']
+
+    def __init__(self, ):
+        """Init """
+        specs = cfnlint.helpers.RESOURCE_SPECS.get('us-east-1')
+        self.property_specs = specs.get('PropertyTypes')
+        self.resource_specs = specs.get('ResourceTypes')
+        for resource_spec in self.resource_specs:
+            self.resource_property_types.append(resource_spec)
+        for property_spec in self.property_specs:
+            self.resource_sub_property_types.append(property_spec)
+
+    def check_value(self, value, path, **kwargs):
+        """Check Value"""
+        matches = list()
+        primitive_type = kwargs.get('primitive_type', {})
+        if isinstance(value, dict) and primitive_type == 'Json':
+            return matches
+        if primitive_type in ['String']:
+            if not isinstance(value, (str, six.text_type, six.string_types)):
+                message = 'Property %s should be of type String' % ('/'.join(map(str, path)))
+                matches.append(RuleMatch(path, message))
+        elif primitive_type in ['Boolean']:
+            if not isinstance(value, (bool)):
+                message = 'Property %s should be of type Boolean' % ('/'.join(map(str, path)))
+                matches.append(RuleMatch(path, message))
+        elif primitive_type in ['Double']:
+            if not isinstance(value, (float, int)):
+                message = 'Property %s should be of type Double' % ('/'.join(map(str, path)))
+                matches.append(RuleMatch(path, message))
+        elif primitive_type in ['Integer']:
+            if not isinstance(value, (int)):
+                message = 'Property %s should be of type Integer' % ('/'.join(map(str, path)))
+                matches.append(RuleMatch(path, message))
+        elif primitive_type in ['Long']:
+            if sys.version_info < (3,):
+                integer_types = (int, long,)  # pylint: disable=undefined-variable
+            else:
+                integer_types = (int,)
+            if not isinstance(value, integer_types):
+                message = 'Property %s should be of type Long' % ('/'.join(map(str, path)))
+                matches.append(RuleMatch(path, message))
+        elif isinstance(value, list):
+            message = 'Property should be of type %s at %s' % (primitive_type, '/'.join(map(str, path)))
+            matches.append(RuleMatch(path, message))
+
+        return matches
+
+    def check(self, cfn, properties, specs, path):
+        """Check itself"""
+        matches = list()
+
+        for prop in properties:
+            if prop in specs:
+                primitive_type = specs.get(prop).get('PrimitiveType')
+                if not primitive_type:
+                    primitive_type = specs.get(prop).get('PrimitiveItemType')
+
+                if primitive_type:
+                    matches.extend(
+                        cfn.check_value(
+                            properties, prop, path,
+                            check_value=self.check_value,
+                            primitive_type=primitive_type
+                        )
+                    )
+
+        return matches
+
+    def match_resource_sub_properties(self, properties, property_type, path, cfn):
+        """Match for sub properties"""
+        matches = list()
+
+        property_specs = self.property_specs.get(property_type, {}).get('Properties', {})
+        matches.extend(self.check(cfn, properties, property_specs, path))
+
+        return matches
+
+    def match_resource_properties(self, properties, resource_type, path, cfn):
+        """Check CloudFormation Properties"""
+        matches = list()
+        resource_specs = self.resource_specs.get(resource_type, {}).get('Properties', {})
+        matches.extend(self.check(cfn, properties, resource_specs, path))
+
+        return matches


Test patch:
diff --git a/test/module/test_cfn_json.py b/test/module/test_cfn_json.py
--- a/test/module/test_cfn_json.py
+++ b/test/module/test_cfn_json.py
@@ -34,7 +34,7 @@ def setUp(self):
         self.filenames = {
             "config_rule": {
                 "filename": 'templates/quickstart/config-rules.json',
-                "failures": 0
+                "failures": 2
             },
             "iam": {
                 "filename": 'templates/quickstart/iam.json',
@@ -46,11 +46,11 @@ def setUp(self):
             },
             "vpc_management": {
                 "filename": 'templates/quickstart/vpc-management.json',
-                "failures": 16
+                "failures": 32
             },
             "vpc": {
                 "filename": 'templates/quickstart/vpc.json',
-                "failures": 1
+                "failures": 42
             }
         }
 
@@ -64,7 +64,7 @@ def test_success_parse(self):
 
             matches = list()
             matches.extend(self.rules.run(filename, cfn, []))
-            assert len(matches) == failures, 'Expected {} failures, got {}'.format(failures, len(matches))
+            assert len(matches) == failures, 'Expected {} failures, got {} on {}'.format(failures, len(matches), filename)
 
     def test_fail_run(self):
         """Test failure run"""
diff --git a/test/module/test_rules_collections.py b/test/module/test_rules_collections.py
--- a/test/module/test_rules_collections.py
+++ b/test/module/test_rules_collections.py
@@ -61,7 +61,7 @@ def test_fail_run(self):
 
         matches = list()
         matches.extend(self.rules.run(filename, cfn, []))
-        assert(len(matches) == 25)
+        assert len(matches) == 27, 'Expected {} failures, got {}'.format(27, len(matches))
 
     def test_fail_sub_properties_run(self):
         """Test failure run"""
@@ -74,4 +74,4 @@ def test_fail_sub_properties_run(self):
 
         matches = list()
         matches.extend(self.rules.run(filename, cfn, []))
-        assert(len(matches) == 2)
+        assert len(matches) == 3, 'Expected {} failures, got {}'.format(2, len(matches))
diff --git a/test/rules/resources/properties/test_properties.py b/test/rules/resources/properties/test_properties.py
--- a/test/rules/resources/properties/test_properties.py
+++ b/test/rules/resources/properties/test_properties.py
@@ -35,7 +35,7 @@ def test_file_positive_template(self):
 
     def test_file_negative(self):
         """Test failure"""
-        self.helper_file_negative('templates/bad/generic.yaml', 7)
+        self.helper_file_negative('templates/bad/generic.yaml', 6)
 
     def test_file_negative_2(self):
         """Failure test"""
@@ -43,4 +43,4 @@ def test_file_negative_2(self):
 
     def test_file_negative_3(self):
         """Failure test"""
-        self.helper_file_negative('templates/bad/resource_properties.yaml', 5)
+        self.helper_file_negative('templates/bad/resource_properties.yaml', 4)
diff --git a/test/templates/good/no_value.yaml b/test/templates/good/no_value.yaml
new file mode 100644
--- /dev/null
+++ b/test/templates/good/no_value.yaml
@@ -0,0 +1,161 @@
+---
+AWSTemplateFormatVersion: '2010-09-09'
+
+Description: MyApp RDS instances
+
+Parameters:
+    pApplicationName:
+        Type: String
+        Description: Application name (typically MyApp).
+        Default: MyApp
+    pMultiAZ:
+        Type: String
+        Description: Set to true to create instances in both AZs, false for one.
+        AllowedValues:
+            - true
+            - false
+    pEnhancedMonitoring:
+        Type: String
+        Description: Set to true to enable RDS enhanced monitoring, false to disable.
+        AllowedValues:
+            - true
+            - false
+    pDBAllocatedStorage:
+        Type: String
+        Description: The amount of disk storage allocated to the database server.
+    pDBParameterGroupFamily:
+        Type: String
+        Description: The RDS parameter group family.
+    pDBInstanceClass:
+        Type: String
+        Description: The RDS instance class.
+    pDBEngine:
+        Type: String
+        Description: The RDS Engine.
+    pDBEngineVersion:
+        Type: String
+        Description: The RDS Engine version.
+    pDBMasterUsername:
+        Type: String
+        Description: The DB Master Username.
+    pDBServerPort:
+        Type: Number
+        Description: The DB engine port. Valid values are 1150-65535 except for 1434, 3389, 47001, 49152, and 49152 through 49156.
+        Default: 1433
+    pProdMonitoringInterval:
+        Type: Number
+        Description: The interval, in seconds, between points when Enhanced Monitoring metrics are collected for the DB instance when running in the Prod stage.
+        Default: 60
+    pDevMonitoringInterval:
+        Type: Number
+        Description: The interval, in seconds, between points when Enhanced Monitoring metrics are collected for the DB instance when running in the Dev stage.
+        Default: 0
+
+Resources:
+
+    rParameterGroup:
+        Type: AWS::RDS::DBParameterGroup
+        Properties:
+            Description: !Join [' ', [!Ref pApplicationName, 'RDS Parameter Group.']]
+            Family: !Ref pDBParameterGroupFamily
+            Tags:
+                - Key: app
+                  Value: !Ref pApplicationName
+                - Key: env
+                  Value:
+                      Fn::ImportValue: !Sub "${pApplicationName}:config:env"
+
+    rDBSubnetGroup:
+        Type: AWS::RDS::DBSubnetGroup
+        Properties:
+            DBSubnetGroupDescription: !Join [' ', [!Ref pApplicationName, 'RDS Subnet Group.']]
+            SubnetIds:
+                - Fn::ImportValue: !Sub "${pApplicationName}:subnet:data:1"
+                - Fn::ImportValue: !Sub "${pApplicationName}:subnet:data:2"
+            Tags:
+                - Key: app
+                  Value: !Ref pApplicationName
+                - Key: env
+                  Value:
+                      Fn::ImportValue: !Sub "${pApplicationName}:config:env"
+
+    rDBPassword:
+        Type: Custom::Secret
+        Properties:
+            Name: !Sub "/passwords/rds/${AWS::StackName}"
+            KeyAlias: alias/aws/ssm
+            Alphabet: abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
+            Length: 30
+            ReturnSecret: true
+            ServiceToken:
+                Fn::Join: [ ":", [ "arn:aws:lambda", !Ref "AWS::Region", ! "Ref": "AWS::AccountId", "function:binxio-cfn-secret-provider" ] ]
+
+    rDBMonitoringRole:
+        Type: AWS::IAM::Role
+        Condition: EnhancedMonitoring
+        Properties:
+            AssumeRolePolicyDocument:
+                Statement:
+                    - Effect: Allow
+                      Principal:
+                          Service: monitoring.rds.amazonaws.com
+                      Action: sts:AssumeRole
+            Policies:
+                - PolicyName: "MyAppRDSEnhancedMonitoring"
+                  PolicyDocument:
+                    Version: '2012-10-17'
+                    Statement:
+                        - Effect: Allow
+                          Action:
+                              - logs:CreateLogGroup
+                              - logs:PutRetentionPolicy
+                          Resource:
+                              - arn:aws:logs:*:*:log-group:RDS*
+                        - Effect: Allow
+                          Action:
+                              - logs:CreateLogStream
+                              - logs:PutLogEvents
+                              - logs:DescribeLogStreams
+                              - logs:GetLogEvents
+                          Resource:
+                              - arn:aws:logs:*:*:log-group:RDS*:log-stream:*
+
+    rDBServerInstance:
+        Type: AWS::RDS::DBInstance
+        Properties:
+            AllocatedStorage: !Ref pDBAllocatedStorage
+            AllowMajorVersionUpgrade: False
+            AutoMinorVersionUpgrade: True
+            BackupRetentionPeriod: 35
+            CopyTagsToSnapshot: True
+            DBInstanceClass: !Ref pDBInstanceClass
+            DBParameterGroupName: !Ref rParameterGroup
+            DBSubnetGroupName: !Ref rDBSubnetGroup
+            Engine: !Ref pDBEngine
+            EngineVersion: !Ref pDBEngineVersion
+            LicenseModel: license-included
+            MasterUserPassword: !GetAtt [ rDBPassword, "Secret" ]
+            MasterUsername: !Ref pDBMasterUsername
+            # MonitoringInterval: !If [ EnhancedMonitoring, !Ref pProdMonitoringInterval, !Ref pDevMonitoringInterval ]
+            MonitoringRoleArn:
+              Fn::If:
+              - EnhancedMonitoring
+              - !GetAtt [ rDBMonitoringRole, Arn ]
+              - !Ref AWS::NoValue
+            MultiAZ: !If [ MultiAZ, true, false ]
+            Port: !Ref pDBServerPort
+            PubliclyAccessible: False
+            StorageEncrypted: True
+            StorageType: gp2
+            Tags:
+                - Key: app
+                  Value: !Ref pApplicationName
+                - Key: env
+                  Value:
+                      Fn::ImportValue: !Sub "${pApplicationName}:config:env"
+            VPCSecurityGroups:
+                - Fn::ImportValue: !Sub "${pApplicationName}:sg:data"
+
+Conditions:
+    MultiAZ: !Equals [ !Ref pMultiAZ, true ]
+    EnhancedMonitoring: !Equals [ !Ref pEnhancedMonitoring, true ]
diff --git a/test/templates/quickstart/test.yaml b/test/templates/quickstart/test.yaml
new file mode 100644
--- /dev/null
+++ b/test/templates/quickstart/test.yaml
@@ -0,0 +1,157 @@
+---
+AWSTemplateFormatVersion: '2010-09-09'
+
+Description: MyApp RDS instances
+
+Parameters:
+    pApplicationName:
+        Type: String
+        Description: Application name (typically MyApp).
+        Default: MyApp
+    pMultiAZ:
+        Type: String
+        Description: Set to true to create instances in both AZs, false for one.
+        AllowedValues:
+            - true
+            - false
+    pEnhancedMonitoring:
+        Type: String
+        Description: Set to true to enable RDS enhanced monitoring, false to disable.
+        AllowedValues:
+            - true
+            - false
+    pDBAllocatedStorage:
+        Type: String
+        Description: The amount of disk storage allocated to the database server.
+    pDBParameterGroupFamily:
+        Type: String
+        Description: The RDS parameter group family.
+    pDBInstanceClass:
+        Type: String
+        Description: The RDS instance class.
+    pDBEngine:
+        Type: String
+        Description: The RDS Engine.
+    pDBEngineVersion:
+        Type: String
+        Description: The RDS Engine version.
+    pDBMasterUsername:
+        Type: String
+        Description: The DB Master Username.
+    pDBServerPort:
+        Type: Number
+        Description: The DB engine port. Valid values are 1150-65535 except for 1434, 3389, 47001, 49152, and 49152 through 49156.
+        Default: 1433
+    pProdMonitoringInterval:
+        Type: Number
+        Description: The interval, in seconds, between points when Enhanced Monitoring metrics are collected for the DB instance when running in the Prod stage.
+        Default: 60
+    pDevMonitoringInterval:
+        Type: Number
+        Description: The interval, in seconds, between points when Enhanced Monitoring metrics are collected for the DB instance when running in the Dev stage.
+        Default: 0
+
+Resources:
+
+    rParameterGroup:
+        Type: AWS::RDS::DBParameterGroup
+        Properties:
+            Description: !Join [' ', [!Ref pApplicationName, 'RDS Parameter Group.']]
+            Family: !Ref pDBParameterGroupFamily
+            Tags:
+                - Key: app
+                  Value: !Ref pApplicationName
+                - Key: env
+                  Value:
+                      Fn::ImportValue: !Sub "${pApplicationName}:config:env"
+
+    rDBSubnetGroup:
+        Type: AWS::RDS::DBSubnetGroup
+        Properties:
+            DBSubnetGroupDescription: !Join [' ', [!Ref pApplicationName, 'RDS Subnet Group.']]
+            SubnetIds:
+                - Fn::ImportValue: !Sub "${pApplicationName}:subnet:data:1"
+                - Fn::ImportValue: !Sub "${pApplicationName}:subnet:data:2"
+            Tags:
+                - Key: app
+                  Value: !Ref pApplicationName
+                - Key: env
+                  Value:
+                      Fn::ImportValue: !Sub "${pApplicationName}:config:env"
+
+    rDBPassword:
+        Type: Custom::Secret
+        Properties:
+            Name: !Sub "/passwords/rds/${AWS::StackName}"
+            KeyAlias: alias/aws/ssm
+            Alphabet: abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
+            Length: 30
+            ReturnSecret: true
+            ServiceToken:
+                Fn::Join: [ ":", [ "arn:aws:lambda", !Ref "AWS::Region", ! "Ref": "AWS::AccountId", "function:binxio-cfn-secret-provider" ] ]
+
+    rDBMonitoringRole:
+        Type: AWS::IAM::Role
+        Condition: EnhancedMonitoring
+        Properties:
+            AssumeRolePolicyDocument:
+                Statement:
+                    - Effect: Allow
+                      Principal:
+                          Service: monitoring.rds.amazonaws.com
+                      Action: sts:AssumeRole
+            Policies:
+                - PolicyName: "MyAppRDSEnhancedMonitoring"
+                  PolicyDocument:
+                    Version: '2012-10-17'
+                    Statement:
+                        - Effect: Allow
+                          Action:
+                              - logs:CreateLogGroup
+                              - logs:PutRetentionPolicy
+                          Resource:
+                              - arn:aws:logs:*:*:log-group:RDS*
+                        - Effect: Allow
+                          Action:
+                              - logs:CreateLogStream
+                              - logs:PutLogEvents
+                              - logs:DescribeLogStreams
+                              - logs:GetLogEvents
+                          Resource:
+                              - arn:aws:logs:*:*:log-group:RDS*:log-stream:*
+
+    rDBServerInstance:
+        Type: AWS::RDS::DBInstance
+        Properties:
+            AllocatedStorage: !Ref pDBAllocatedStorage
+            AllowMajorVersionUpgrade: False
+            AutoMinorVersionUpgrade: True
+            BackupRetentionPeriod: 35
+            CopyTagsToSnapshot: True
+            DBInstanceClass: !Ref pDBInstanceClass
+            DBParameterGroupName: !Ref rParameterGroup
+            DBSubnetGroupName: !Ref rDBSubnetGroup
+            Engine: !Ref pDBEngine
+            EngineVersion: !Ref pDBEngineVersion
+            LicenseModel: license-included
+            MasterUserPassword: !GetAtt [ rDBPassword, "Secret" ]
+            MasterUsername: !Ref pDBMasterUsername
+            MonitoringInterval: !If [ EnhancedMonitoring, !Ref pProdMonitoringInterval, !Ref pDevMonitoringInterval ]
+            MonitoringRoleArn: !If [ EnhancedMonitoring, !GetAtt rDBMonitoringRole.Arn, !Ref AWS::NoValue ]
+            MultiAZ: !If [ MultiAZ, true, false ]
+            Port: !Ref pDBServerPort
+            PubliclyAccessible: False
+            StorageEncrypted: True
+            StorageType: gp2
+            Tags:
+                - Key: app
+                  Value: !Ref pApplicationName
+                - Key: env
+                  Value:
+                      Fn::ImportValue: !Sub "${pApplicationName}:config:env"
+            VPCSecurityGroups:
+                - Fn::ImportValue: !Sub "${pApplicationName}:sg:data"
+
+Conditions:
+    MultiAZ: !Equals [ !Ref pMultiAZ, true ]
+    EnhancedMonitoring: !Equals [ !Ref pEnhancedMonitoring, true ]