Repository: aws-cloudformation/cfn-lint

Issue:
Check for AllocatedStorage when DBInstance engine is not Aurora
*cfn-lint version: (`cfn-lint --version`)* 0.28.2
*Using latest spec*

*Description of issue.*

`cfn-lint` does not currently warn/error when an `AWS::RDS::DBInstance` is missing the `AllocatedStorage` property. This property is required when using non-Aurora engines, and is not when using Aurora.



Discussion:

Resolving patch:
diff --git a/src/cfnlint/rules/resources/rds/AuroraDBInstanceProperties.py b/src/cfnlint/rules/resources/rds/AuroraDBInstanceProperties.py
new file mode 100644
--- /dev/null
+++ b/src/cfnlint/rules/resources/rds/AuroraDBInstanceProperties.py
@@ -0,0 +1,66 @@
+"""
+Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+SPDX-License-Identifier: MIT-0
+"""
+import six
+from cfnlint.rules import CloudFormationLintRule
+from cfnlint.rules import RuleMatch
+
+
+class AuroraDBInstanceProperties(CloudFormationLintRule):
+    """Aurora DB instances have a lot properties that can't be set and vice and versa"""
+    id = 'E3029'
+    shortdesc = 'Aurora instances don\'t require certain properties'
+    description = 'Certain properties are not reuqired when using the Aurora engine for AWS::RDS::DBInstance'
+    source_url = 'https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-rds-database-instance.html'
+    tags = ['resources', 'rds']
+    aurora_not_required_props = [
+        'AllocatedStorage',
+        'BackupRetentionPeriod',
+        'CopyTagsToSnapshot',
+        'DeletionProtection',
+        'EnableIAMDatabaseAuthentication',
+        'MasterUserPassword',
+        'StorageEncrypted',
+    ]
+    aurora_engines = [
+        'aurora',
+        'aurora-mysql',
+        'aurora-postgresql',
+    ]
+
+    def __init__(self):
+        """Init"""
+        super(AuroraDBInstanceProperties, self).__init__()
+        self.resource_property_types = ['AWS::RDS::DBInstance']
+
+    def check(self, properties, path, cfn):
+        """Check itself"""
+        matches = []
+        property_sets = cfn.get_object_without_conditions(
+            properties, ['Engine'] + self.aurora_not_required_props)
+        for property_set in property_sets:
+            properties = property_set.get('Object')
+            scenario = property_set.get('Scenario')
+            engine_sets = properties.get_safe('Engine', type_t=six.string_types)
+            for engine, _ in engine_sets:
+                if engine in self.aurora_engines:
+                    for prop in properties:
+                        if prop in self.aurora_not_required_props:
+                            path_prop = path[:] + [prop]
+                            message = 'You cannot specify {} for Aurora AWS::RDS::DBInstance at {}'
+                            if scenario is None:
+                                matches.append(
+                                    RuleMatch(path_prop, message.format(prop, '/'.join(map(str, path_prop)))))
+                            else:
+                                scenario_text = ' and '.join(
+                                    ['when condition "%s" is %s' % (k, v) for (k, v) in scenario.items()])
+                                matches.append(
+                                    RuleMatch(path_prop, message.format(prop, '/'.join(map(str, path_prop)) + ' ' + scenario_text)))
+        return matches
+
+    def match_resource_properties(self, properties, _, path, cfn):
+        """Match for sub properties"""
+        matches = []
+        matches.extend(self.check(properties, path, cfn))
+        return matches


Test patch:
diff --git a/test/fixtures/templates/bad/resources/rds/aurora_dbinstance_properties.yaml b/test/fixtures/templates/bad/resources/rds/aurora_dbinstance_properties.yaml
new file mode 100644
--- /dev/null
+++ b/test/fixtures/templates/bad/resources/rds/aurora_dbinstance_properties.yaml
@@ -0,0 +1,40 @@
+---
+AWSTemplateFormatVersion: 2010-09-09
+Description: "RDS Storage Encrypted"
+Parameters:
+  Engine:
+    Type: String
+  UseAurora:
+    Type: String
+    AllowedValues:
+      - "true"
+      - "false"
+Conditions:
+  IsAurora:
+    Fn::Or:
+      - !Equals [!Ref Engine, "aurora"]
+      - !Equals [!Ref Engine, "aurora-mysql"]
+      - !Equals [!Ref Engine, "aurora-postgresql"]
+  IsAurora2: !Equals [!Ref UseAurora, "true"]
+Resources:
+  MyDBSmall:
+    Type: "AWS::RDS::DBInstance"
+    Properties:
+      Engine: aurora
+      AllocatedStorage: "100"
+      DBInstanceClass: db.r3.2xlarge
+      StorageEncrypted: true
+  MyDbInstance2:
+    Type: "AWS::RDS::DBInstance"
+    Properties:
+      Engine: !Ref Engine
+      DBInstanceClass: db.r3.2xlarge
+      # While this is bad we can't determine the Engine so skipping
+      AllocatedStorage: !If [IsAurora, "100", !Ref "AWS::NoValue"]
+  MySqlInstance:
+    Type: "AWS::RDS::DBInstance"
+    Properties:
+      Engine: !If [IsAurora2, aurora-mysql, mysql]
+      DBInstanceClass: db.r3.2xlarge
+      # Since we can figure out the engine this one should fail
+      AllocatedStorage: !If [IsAurora2, "100", !Ref "AWS::NoValue"]
diff --git a/test/fixtures/templates/good/resources/rds/aurora_dbinstance_properties.yaml b/test/fixtures/templates/good/resources/rds/aurora_dbinstance_properties.yaml
new file mode 100644
--- /dev/null
+++ b/test/fixtures/templates/good/resources/rds/aurora_dbinstance_properties.yaml
@@ -0,0 +1,38 @@
+---
+AWSTemplateFormatVersion: 2010-09-09
+Description: "RDS Storage Encrypted"
+Parameters:
+  Engine:
+    Type: String
+  UseAurora:
+    Type: String
+    AllowedValues:
+      - "true"
+      - "false"
+Conditions:
+  IsAurora:
+    Fn::Or:
+      - !Equals [!Ref Engine, "aurora"]
+      - !Equals [!Ref Engine, "aurora-mysql"]
+      - !Equals [!Ref Engine, "aurora-postgresql"]
+  IsAurora2: !Equals [!Ref UseAurora, "true"]
+Resources:
+  MyDbInstance1:
+    Type: "AWS::RDS::DBInstance"
+    Properties:
+      Engine: mysql
+      AllocatedStorage: "100"
+      DBInstanceClass: db.r3.2xlarge
+      StorageEncrypted: true
+  MyDbInstance2:
+    Type: "AWS::RDS::DBInstance"
+    Properties:
+      Engine: !Ref Engine
+      DBInstanceClass: db.r3.2xlarge
+      AllocatedStorage: !If [IsAurora, !Ref "AWS::NoValue", "100"]
+  MySqlInstance:
+    Type: "AWS::RDS::DBInstance"
+    Properties:
+      Engine: !If [IsAurora2, aurora-mysql, mysql]
+      DBInstanceClass: db.r3.2xlarge
+      AllocatedStorage: !If [IsAurora2, !Ref "AWS::NoValue", "100"]
diff --git a/test/unit/rules/resources/rds/test_auroa_dbinstance_properties.py b/test/unit/rules/resources/rds/test_auroa_dbinstance_properties.py
new file mode 100644
--- /dev/null
+++ b/test/unit/rules/resources/rds/test_auroa_dbinstance_properties.py
@@ -0,0 +1,27 @@
+"""
+Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+SPDX-License-Identifier: MIT-0
+"""
+from test.unit.rules import BaseRuleTestCase
+from cfnlint.rules.resources.rds.AuroraDBInstanceProperties import AuroraDBInstanceProperties  # pylint: disable=E0401
+
+
+class TestAuroraDBInstanceProperties(BaseRuleTestCase):
+    """Test RDS Auror Auto Scaling Configurartion"""
+
+    def setUp(self):
+        """Setup"""
+        super(TestAuroraDBInstanceProperties, self).setUp()
+        self.collection.register(AuroraDBInstanceProperties())
+        self.success_templates = [
+            'test/fixtures/templates/good/resources/rds/aurora_dbinstance_properties.yaml'
+        ]
+
+    def test_file_positive(self):
+        """Test Positive"""
+        self.helper_file_positive()
+
+    def test_file_negative_alias(self):
+        """Test failure"""
+        self.helper_file_negative(
+            'test/fixtures/templates/bad/resources/rds/aurora_dbinstance_properties.yaml', 3)