diff --git a/src/cfnlint/rules/resources/rds/AuroraDBInstanceProperties.py b/src/cfnlint/rules/resources/rds/AuroraDBInstanceProperties.py new file mode 100644 index 0000000000..a8303d5f46 --- /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 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 index 0000000000..0abac1593c --- /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 index 0000000000..d63dfdb28f --- /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 index 0000000000..c471271c2e --- /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)