From c38883fec40a81d92ab8346f4fc93dabede90642 Mon Sep 17 00:00:00 2001 From: Niels Laukens Date: Mon, 3 Jun 2019 11:46:53 +0200 Subject: [PATCH 1/5] Add support for Rules Rules are a feature of AWS Service Catalogue [1] but is undocumented for pure CloudFormation. However, it does work in vanilla CloudFormation as well [2]. This adds minimal support for Rules in templates. It provides a bare dict, and exports it to JSON/YAML. [1]: https://docs.aws.amazon.com/servicecatalog/latest/adminguide/reference-template_constraint_rules.html [2]: https://www.cloudar.be/awsblog/undocumented-feature-using-template-constraint-rules-in-cloudformation/ --- tests/test_template.py | 28 ++++++++++++++++++++++++++++ troposphere/__init__.py | 4 ++++ 2 files changed, 32 insertions(+) diff --git a/tests/test_template.py b/tests/test_template.py index de7c79b96..186012aad 100644 --- a/tests/test_template.py +++ b/tests/test_template.py @@ -163,5 +163,33 @@ def test_parameter_group(self): }) +class TestRules(unittest.TestCase): + def test_rules(self): + t = Template() + t.add_parameter("One") + t.add_parameter("Two") + + rule = { + 'ValidateEqual': { + 'Assertions': [ + { + 'Assert': { + "Fn::Equals": [ + {'Ref': 'One'}, + {'Ref': 'Two'}, + ], + }, + }, + ], + }, + } + t.rules.update(rule) + + self.assertEqual(t.rules, rule) + + rendered = t.to_dict() + self.assertEqual(rendered['Rules'], rule) + + if __name__ == '__main__': unittest.main() diff --git a/troposphere/__init__.py b/troposphere/__init__.py index 09a614c56..380ffc056 100644 --- a/troposphere/__init__.py +++ b/troposphere/__init__.py @@ -574,6 +574,7 @@ class Template(object): 'Mappings': (dict, False), 'Resources': (dict, False), 'Outputs': (dict, False), + 'Rules': (dict, False), } def __init__(self, Description=None, Metadata=None): # noqa: N803 @@ -584,6 +585,7 @@ def __init__(self, Description=None, Metadata=None): # noqa: N803 self.outputs = {} self.parameters = {} self.resources = {} + self.rules = {} self.version = None self.transform = None @@ -703,6 +705,8 @@ def to_dict(self): t['AWSTemplateFormatVersion'] = self.version if self.transform: t['Transform'] = self.transform + if self.rules: + t['Rules'] = self.rules t['Resources'] = self.resources return encode_to_dict(t) From 9a250b7acd528a3d62c724cb2bdf9302dcb87784 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raphae=CC=88l=20Kolm?= Date: Fri, 28 Jun 2019 14:54:01 +0200 Subject: [PATCH 2/5] Add Template.add_rule() function to be consistent with the Template API --- troposphere/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/troposphere/__init__.py b/troposphere/__init__.py index 05a0cc6be..66bed4360 100644 --- a/troposphere/__init__.py +++ b/troposphere/__init__.py @@ -668,6 +668,9 @@ def add_resource(self, resource): % MAX_RESOURCES) return self._update(self.resources, resource) + def add_rule(self, name, rule): + self.rules[name] = rule + def set_version(self, version=None): if version: self.version = version From 7c0951a3af29bf49524de8b2b23c38bab90f07dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raphae=CC=88l=20Kolm?= Date: Fri, 28 Jun 2019 23:26:17 +0200 Subject: [PATCH 3/5] Write doc for add_rule() --- troposphere/__init__.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/troposphere/__init__.py b/troposphere/__init__.py index 66bed4360..cd08d5ece 100644 --- a/troposphere/__init__.py +++ b/troposphere/__init__.py @@ -669,6 +669,15 @@ def add_resource(self, resource): return self._update(self.resources, resource) def add_rule(self, name, rule): + """ + Add a Rule to the template to enforce extra constraints on the + parameters. As of June 2019 rules are undocumented in CloudFormation + but have the same syntax and behaviour as in ServiceCatalog: + https://docs.aws.amazon.com/servicecatalog/latest/adminguide/reference-template_constraint_rules.html + + :param rule: a dict with 'Assertions' (mandatory) and 'RuleCondition' + (optional) keys + """ self.rules[name] = rule def set_version(self, version=None): From 8ab1eea70c7f48a7463d1311393dbaaf6bc5a234 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raphae=CC=88l=20Kolm?= Date: Fri, 28 Jun 2019 23:39:58 +0200 Subject: [PATCH 4/5] Adapt test case to the add_rule() interface --- tests/test_template.py | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/tests/test_template.py b/tests/test_template.py index 186012aad..261055afb 100644 --- a/tests/test_template.py +++ b/tests/test_template.py @@ -170,25 +170,23 @@ def test_rules(self): t.add_parameter("Two") rule = { - 'ValidateEqual': { - 'Assertions': [ - { - 'Assert': { - "Fn::Equals": [ - {'Ref': 'One'}, - {'Ref': 'Two'}, - ], - }, + "Assertions": [ + { + "Assert": { + "Fn::Equals": [ + {"Ref": "One"}, + {"Ref": "Two"}, + ], }, - ], - }, + }, + ], } - t.rules.update(rule) + t.add_rule("ValidateEqual", rule) - self.assertEqual(t.rules, rule) + self.assertTrue("ValidateEqual" in t.rules) rendered = t.to_dict() - self.assertEqual(rendered['Rules'], rule) + self.assertEqual(rendered["Rules"]["ValidateEqual"], rule) if __name__ == '__main__': From 4c750326121aa1dee9b218ecdf6b37849d69702d Mon Sep 17 00:00:00 2001 From: Niels Laukens Date: Sat, 29 Jun 2019 06:48:58 +0200 Subject: [PATCH 5/5] Add duplicate name check in add_rule --- troposphere/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/troposphere/__init__.py b/troposphere/__init__.py index cd08d5ece..6f4dd4a19 100644 --- a/troposphere/__init__.py +++ b/troposphere/__init__.py @@ -678,6 +678,9 @@ def add_rule(self, name, rule): :param rule: a dict with 'Assertions' (mandatory) and 'RuleCondition' (optional) keys """ + # TODO: check maximum number of Rules, and enforce limit. + if name in self.rules: + self.handle_duplicate_key(name) self.rules[name] = rule def set_version(self, version=None):