Repository: cloudtools/troposphere

Issue:
Template validation
Hi,

I'd like to add validaters on the template object for these limits:
http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cloudformation-limits.html

Would this be better done by adding checks to the add_condition/add_resource,etc methods on the template objects? Or should I add a validate method to the template object, and call that as part of to_json?


Discussion:

Resolving patch:
diff --git a/troposphere/__init__.py b/troposphere/__init__.py
--- a/troposphere/__init__.py
+++ b/troposphere/__init__.py
@@ -27,6 +27,11 @@
 AWS_STACK_ID = 'AWS::StackId'
 AWS_STACK_NAME = 'AWS::StackName'
 
+# Template Limits
+MAX_PARAMETERS = 60
+MAX_RESOURCES = 200
+PARAMETER_TITLE_MAX = 255
+
 valid_names = re.compile(r'^[a-zA-Z0-9]+$')
 
 
@@ -490,6 +495,9 @@ def add_metadata(self, metadata):
         self.metadata = metadata
 
     def add_condition(self, name, condition):
+        if len(self.conditions) >= 60:
+            raise ValueError('Maximum of 60 conditions per template reached')
+
         self.conditions[name] = condition
 
     def handle_duplicate_key(self, key):
@@ -514,9 +522,14 @@ def add_mapping(self, name, mapping):
         self.mappings[name] = mapping
 
     def add_parameter(self, parameter):
+        if len(self.parameters) >= MAX_PARAMETERS:
+            raise ValueError('Maximum parameters %d reached' % MAX_PARAMETERS)
         return self._update(self.parameters, parameter)
 
     def add_resource(self, resource):
+        if len(self.resources) >= MAX_RESOURCES:
+            raise ValueError('Maximum number of resources %d reached'
+                             % MAX_RESOURCES)
         return self._update(self.resources, resource)
 
     def add_version(self, version=None):
@@ -585,6 +598,12 @@ class Parameter(AWSDeclaration):
         'ConstraintDescription': (basestring, False),
     }
 
+    def validate_title(self):
+        if len(self.title) > PARAMETER_TITLE_MAX:
+            raise ValueError("Parameter title can be no longer than "
+                             "%d characters" % PARAMETER_TITLE_MAX)
+        super(Parameter, self).validate_title()
+
     def validate(self):
         if self.properties['Type'] != 'String':
             for p in self.STRING_PROPERTIES:


Test patch:
diff --git a/tests/test_parameters.py b/tests/test_parameters.py
new file mode 100644
--- /dev/null
+++ b/tests/test_parameters.py
@@ -0,0 +1,14 @@
+import unittest
+
+from troposphere import Parameter
+
+
+class TestInitArguments(unittest.TestCase):
+    def test_title_max_length(self):
+        title = 'i' * 256
+        with self.assertRaises(ValueError):
+            Parameter(title, Type='String')
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/test_template.py b/tests/test_template.py
--- a/tests/test_template.py
+++ b/tests/test_template.py
@@ -1,6 +1,7 @@
 import unittest
 
-from troposphere import Template
+from troposphere import Template, Parameter
+from troposphere.s3 import Bucket
 
 
 class TestInitArguments(unittest.TestCase):
@@ -23,5 +24,21 @@ def test_metadata(self):
         self.assertEqual(template.metadata, value)
 
 
+class TestValidate(unittest.TestCase):
+    def test_max_parameters(self):
+        template = Template()
+        for i in range(1, 61):
+            template.add_parameter(Parameter("parameter%d" % i, Type='String'))
+        with self.assertRaises(ValueError):
+            template.add_parameter(Parameter("Parameter61", Type='String'))
+
+    def test_max_resources(self):
+        template = Template()
+        for i in range(1, 201):
+            template.add_resource(Bucket(str(i)))
+        with self.assertRaises(ValueError):
+            template.add_resource(Bucket(str(201)))
+
+
 if __name__ == '__main__':
     unittest.main()