Repository: aws-cloudformation/cfn-lint

Issue:
Feature Request: Warning if DeletionPolicy is not specified
Sorry for not implementing and submitting pull request but currently have no time for that.

DeletionPolicy is not required in for example DynamoDB Table and defaults to Delete.
That is not a good default value IMHO so always setting DeletionPolicy explicitly would be a nice recommendation.


Discussion:
Cool "Best Practice" style addition.  I'll label it as an enhancement
Thanks :)

Resolving patch:
diff --git a/src/cfnlint/rules/resources/dynamodb/TableDeletionPolicy.py b/src/cfnlint/rules/resources/dynamodb/TableDeletionPolicy.py
new file mode 100644
--- /dev/null
+++ b/src/cfnlint/rules/resources/dynamodb/TableDeletionPolicy.py
@@ -0,0 +1,43 @@
+"""
+  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.
+"""
+from cfnlint import CloudFormationLintRule
+from cfnlint import RuleMatch
+
+
+class TableDeletionPolicy(CloudFormationLintRule):
+    """Check Dynamo DB Deletion Policy"""
+    id = 'I3011'
+    shortdesc = 'Check DynamoDB tables have a set DeletionPolicy'
+    description = 'The default action when removing a DynamoDB Table is to ' \
+                  'delete it. This check requires you to specifically set a DeletionPolicy ' \
+                  'and you know the risks'
+    source_url = 'https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-attribute-deletionpolicy.html'
+    tags = ['resources', 'dynamodb']
+
+    def match(self, cfn):
+        """Check CloudFormation DynamDB Tables"""
+        matches = []
+
+        resources = cfn.get_resources(resource_type=['AWS::DynamoDB::Table'])
+        for r_name, r_values in resources.items():
+            if not r_values.get('DeletionPolicy'):
+                path = ['Resources', r_name]
+                message = 'The default action on removal of a DynamoDB is to delete it. ' \
+                          'Set a DeletionPolicy and specify either \'retain\' or \'delete\'.'
+                matches.append(RuleMatch(path, message))
+
+        return matches
diff --git a/src/cfnlint/rules/resources/dynamodb/__init__.py b/src/cfnlint/rules/resources/dynamodb/__init__.py
new file mode 100644
--- /dev/null
+++ b/src/cfnlint/rules/resources/dynamodb/__init__.py
@@ -0,0 +1,16 @@
+"""
+  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.
+"""


Test patch:
diff --git a/test/fixtures/templates/bad/resources/dynamodb/delete_policy.yaml b/test/fixtures/templates/bad/resources/dynamodb/delete_policy.yaml
new file mode 100644
--- /dev/null
+++ b/test/fixtures/templates/bad/resources/dynamodb/delete_policy.yaml
@@ -0,0 +1,16 @@
+---
+AWSTemplateFormatVersion: "2010-09-09"
+Resources:
+  myTable:
+    Type: AWS::DynamoDB::Table
+    Properties:
+      KeySchema:
+        -
+          AttributeName: "ArtistId"
+          KeyType: "HASH"
+        -
+          AttributeName: "Concert"
+          KeyType: "RANGE"
+      ProvisionedThroughput:
+        ReadCapacityUnits: 5
+        WriteCapacityUnits: 5
diff --git a/test/fixtures/templates/good/resources/dynamodb/delete_policy.yaml b/test/fixtures/templates/good/resources/dynamodb/delete_policy.yaml
new file mode 100644
--- /dev/null
+++ b/test/fixtures/templates/good/resources/dynamodb/delete_policy.yaml
@@ -0,0 +1,17 @@
+---
+AWSTemplateFormatVersion: "2010-09-09"
+Resources:
+  myTable:
+    Type: AWS::DynamoDB::Table
+    DeletionPolicy: retain
+    Properties:
+      KeySchema:
+        -
+          AttributeName: "ArtistId"
+          KeyType: "HASH"
+        -
+          AttributeName: "Concert"
+          KeyType: "RANGE"
+      ProvisionedThroughput:
+        ReadCapacityUnits: 5
+        WriteCapacityUnits: 5
diff --git a/test/rules/resources/dynamodb/__init__.py b/test/rules/resources/dynamodb/__init__.py
new file mode 100644
--- /dev/null
+++ b/test/rules/resources/dynamodb/__init__.py
@@ -0,0 +1,16 @@
+"""
+  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.
+"""
diff --git a/test/rules/resources/dynamodb/test_delete_policy.py b/test/rules/resources/dynamodb/test_delete_policy.py
new file mode 100644
--- /dev/null
+++ b/test/rules/resources/dynamodb/test_delete_policy.py
@@ -0,0 +1,37 @@
+"""
+  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.
+"""
+from cfnlint.rules.resources.dynamodb.TableDeletionPolicy import TableDeletionPolicy  # pylint: disable=E0401
+from ... import BaseRuleTestCase
+
+
+class TestTableDeletionPolicy(BaseRuleTestCase):
+    """Test StateMachine for Step Functions"""
+    def setUp(self):
+        """Setup"""
+        super(TestTableDeletionPolicy, self).setUp()
+        self.collection.register(TableDeletionPolicy())
+        self.success_templates = [
+            'fixtures/templates/good/resources/dynamodb/delete_policy.yaml'
+        ]
+
+    def test_file_positive(self):
+        """Test Positive"""
+        self.helper_file_positive()
+
+    def test_file_negative_alias(self):
+        """Test failure"""
+        self.helper_file_negative('fixtures/templates/bad/resources/dynamodb/delete_policy.yaml', 1)