From 713661d8883b285d0b4c3664b5ea54b14d639607 Mon Sep 17 00:00:00 2001 From: Alex Harvey Date: Sat, 17 Feb 2024 13:09:31 +1100 Subject: [PATCH 01/11] Add a proof of concept for type checking --- sceptre/stack.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/sceptre/stack.py b/sceptre/stack.py index 8a633096c..88938156a 100644 --- a/sceptre/stack.py +++ b/sceptre/stack.py @@ -9,7 +9,7 @@ import logging -from typing import List, Any, Optional +from typing import List, Dict, Any, Optional from deprecation import deprecated from sceptre import __version__ @@ -262,7 +262,7 @@ def __init__( ) self.s3_details = s3_details - self.parameters = parameters or {} + self.parameters = self._ensure_parameters(parameters or {}) self.sceptre_user_data = sceptre_user_data or {} self.notifications = notifications or [] @@ -275,6 +275,13 @@ def _ensure_boolean(self, config_name: str, value: Any) -> bool: ) return value + def _ensure_parameters(self, parameters: Dict[str, Any]) -> Dict[str, str]: + if not all(isinstance(value, str) for value in parameters.values()): + raise InvalidConfigFileError( + f"{self.name}: Values for parameters must be strings, got {parameters}" + ) + return parameters + def __repr__(self): return ( "sceptre.stack.Stack(" From a624c48dc024ff0f4961d42f92b64392bf12e210 Mon Sep 17 00:00:00 2001 From: Alex Harvey Date: Sat, 17 Feb 2024 16:12:40 +1100 Subject: [PATCH 02/11] more --- sceptre/stack.py | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/sceptre/stack.py b/sceptre/stack.py index 88938156a..98853d65e 100644 --- a/sceptre/stack.py +++ b/sceptre/stack.py @@ -9,7 +9,7 @@ import logging -from typing import List, Dict, Any, Optional +from typing import List, Dict, Union, Any, Optional from deprecation import deprecated from sceptre import __version__ @@ -26,6 +26,7 @@ ResolvableValueProperty, RecursiveResolve, PlaceholderType, + Resolver, ) from sceptre.template import Template @@ -275,10 +276,24 @@ def _ensure_boolean(self, config_name: str, value: Any) -> bool: ) return value - def _ensure_parameters(self, parameters: Dict[str, Any]) -> Dict[str, str]: - if not all(isinstance(value, str) for value in parameters.values()): + def _ensure_parameters( + self, parameters: Dict[str, Any] + ) -> Dict[str, Union[str, List[str], Resolver]]: + """Ensure CloudFormation parameters are of valid types""" + + def is_valid(value: Any) -> bool: + return ( + isinstance(value, str) + or ( + isinstance(value, list) + and all(isinstance(item, str) for item in value) + ) + or isinstance(value, Resolver) + ) + + if not all(is_valid(value) for value in parameters.values()): raise InvalidConfigFileError( - f"{self.name}: Values for parameters must be strings, got {parameters}" + f"{self.name}: Values for parameters must be strings, lists or resolvers, got {parameters}" ) return parameters From 3d5a4af6d36c2356f0df1cba87f636871b79905c Mon Sep 17 00:00:00 2001 From: Alex Harvey Date: Sat, 17 Feb 2024 16:30:04 +1100 Subject: [PATCH 03/11] fix-tests --- tests/test_stack.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/test_stack.py b/tests/test_stack.py index 7e843e47f..706434815 100644 --- a/tests/test_stack.py +++ b/tests/test_stack.py @@ -183,6 +183,36 @@ def test_init__non_boolean_obsolete_value__raises_invalid_config_file_error(self obsolete="true", ) + @pytest.mark.parametrize("parameters", [ + {"someNum": 1}, + {"someBool": True}, + {"aBadList": [1, 2, 3]} + ]) + def test_init__invalid_types_raise_invalid_config_file_error(self, parameters): + with pytest.raises(InvalidConfigFileError): + Stack( + name="stack_name", + project_code="project_code", + template_handler_config={"type": "file"}, + region="region", + parameters=parameters + ) + + @pytest.mark.parametrize("parameters", [ + {"someNum": "1"}, + {"someBool": "true"}, + {"aBadList": ["1", "2", "3"]} + ]) + def test_init__valid_types(self, parameters): + stack = Stack( + name="stack_name", + project_code="project_code", + template_handler_config={"type": "file"}, + region="region", + parameters=parameters + ) + assert stack.name == "stack_name" + def test_stack_repr(self): assert ( self.stack.__repr__() == "sceptre.stack.Stack(" From 8bd7099322fe749bcbc08d7615d8e3e2551f4bf8 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 17 Feb 2024 05:30:25 +0000 Subject: [PATCH 04/11] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/test_stack.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/tests/test_stack.py b/tests/test_stack.py index 706434815..a30f21238 100644 --- a/tests/test_stack.py +++ b/tests/test_stack.py @@ -183,11 +183,9 @@ def test_init__non_boolean_obsolete_value__raises_invalid_config_file_error(self obsolete="true", ) - @pytest.mark.parametrize("parameters", [ - {"someNum": 1}, - {"someBool": True}, - {"aBadList": [1, 2, 3]} - ]) + @pytest.mark.parametrize( + "parameters", [{"someNum": 1}, {"someBool": True}, {"aBadList": [1, 2, 3]}] + ) def test_init__invalid_types_raise_invalid_config_file_error(self, parameters): with pytest.raises(InvalidConfigFileError): Stack( @@ -195,21 +193,20 @@ def test_init__invalid_types_raise_invalid_config_file_error(self, parameters): project_code="project_code", template_handler_config={"type": "file"}, region="region", - parameters=parameters + parameters=parameters, ) - @pytest.mark.parametrize("parameters", [ - {"someNum": "1"}, - {"someBool": "true"}, - {"aBadList": ["1", "2", "3"]} - ]) + @pytest.mark.parametrize( + "parameters", + [{"someNum": "1"}, {"someBool": "true"}, {"aBadList": ["1", "2", "3"]}], + ) def test_init__valid_types(self, parameters): stack = Stack( name="stack_name", project_code="project_code", template_handler_config={"type": "file"}, region="region", - parameters=parameters + parameters=parameters, ) assert stack.name == "stack_name" From e073c0d36745158e4ee9102b9929cb45c52a34a8 Mon Sep 17 00:00:00 2001 From: Alex Harvey Date: Sat, 17 Feb 2024 16:39:52 +1100 Subject: [PATCH 05/11] fix-tests --- tests/test_stack.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_stack.py b/tests/test_stack.py index a30f21238..d62e7289c 100644 --- a/tests/test_stack.py +++ b/tests/test_stack.py @@ -198,7 +198,7 @@ def test_init__invalid_types_raise_invalid_config_file_error(self, parameters): @pytest.mark.parametrize( "parameters", - [{"someNum": "1"}, {"someBool": "true"}, {"aBadList": ["1", "2", "3"]}], + [{"someNum": "1"}, {"someBool": "true"}, {"aList": ["1", "2", "3"]}], ) def test_init__valid_types(self, parameters): stack = Stack( From 134c484dade70cd45f848b584d16d666a2bbeec3 Mon Sep 17 00:00:00 2001 From: Alex Harvey Date: Sat, 17 Feb 2024 16:49:07 +1100 Subject: [PATCH 06/11] more --- sceptre/stack.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/sceptre/stack.py b/sceptre/stack.py index 98853d65e..95e6c329e 100644 --- a/sceptre/stack.py +++ b/sceptre/stack.py @@ -286,7 +286,10 @@ def is_valid(value: Any) -> bool: isinstance(value, str) or ( isinstance(value, list) - and all(isinstance(item, str) for item in value) + and all( + isinstance(item, str) or isinstance(item, Resolver) + for item in value + ) ) or isinstance(value, Resolver) ) From db9bb62b5faf41f8e0d393f28901c6a9cdc4bac2 Mon Sep 17 00:00:00 2001 From: Alex Harvey Date: Sat, 17 Feb 2024 16:50:10 +1100 Subject: [PATCH 07/11] more --- sceptre/stack.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sceptre/stack.py b/sceptre/stack.py index 95e6c329e..fa5639a8b 100644 --- a/sceptre/stack.py +++ b/sceptre/stack.py @@ -278,7 +278,7 @@ def _ensure_boolean(self, config_name: str, value: Any) -> bool: def _ensure_parameters( self, parameters: Dict[str, Any] - ) -> Dict[str, Union[str, List[str], Resolver]]: + ) -> Dict[str, Union[str, List[Union[str, Resolver]], Resolver]]: """Ensure CloudFormation parameters are of valid types""" def is_valid(value: Any) -> bool: From 2f3e6aedab72b040e2b9f3db0605037b86af0728 Mon Sep 17 00:00:00 2001 From: Alex Harvey Date: Sat, 17 Feb 2024 19:10:53 +1100 Subject: [PATCH 08/11] more --- tests/test_stack.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/tests/test_stack.py b/tests/test_stack.py index d62e7289c..6d679655e 100644 --- a/tests/test_stack.py +++ b/tests/test_stack.py @@ -40,6 +40,11 @@ def stack_factory(**kwargs): return Stack(**call_kwargs) +class FakeResolver(Resolver): + def resolve(self): + return "Fake" + + class TestStack(object): def setup_method(self, test_method): self.stack = Stack( @@ -198,7 +203,12 @@ def test_init__invalid_types_raise_invalid_config_file_error(self, parameters): @pytest.mark.parametrize( "parameters", - [{"someNum": "1"}, {"someBool": "true"}, {"aList": ["1", "2", "3"]}], + [ + {"someNum": "1"}, + {"someBool": "true"}, + {"aList": ["aString", FakeResolver()]}, + {"aResolver": FakeResolver()}, + ], ) def test_init__valid_types(self, parameters): stack = Stack( @@ -275,14 +285,10 @@ def resolve(self): def test_configuration_manager__sceptre_role_returns_value__returns_connection_manager_with_that_role( self, ): - class FakeResolver(Resolver): - def resolve(self): - return "role" - self.stack.sceptre_role = FakeResolver() connection_manager = self.stack.connection_manager - assert connection_manager.sceptre_role == "role" + assert connection_manager.sceptre_role == "Fake" @fail_if_not_removed def test_iam_role__is_removed_on_removal_version(self): From 607081c31c96a768a8196f5ce9eeed805422201b Mon Sep 17 00:00:00 2001 From: Alex Harvey Date: Sat, 17 Feb 2024 19:13:27 +1100 Subject: [PATCH 09/11] more --- tests/test_stack.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_stack.py b/tests/test_stack.py index 6d679655e..b4de22378 100644 --- a/tests/test_stack.py +++ b/tests/test_stack.py @@ -218,7 +218,7 @@ def test_init__valid_types(self, parameters): region="region", parameters=parameters, ) - assert stack.name == "stack_name" + assert isinstance(stack, Stack) def test_stack_repr(self): assert ( From 5a7437d77a9deec44d1548131874c8e7e11055e6 Mon Sep 17 00:00:00 2001 From: Alex Harvey Date: Sat, 17 Feb 2024 19:19:16 +1100 Subject: [PATCH 10/11] more --- tests/test_stack.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_stack.py b/tests/test_stack.py index b4de22378..0c1af37ab 100644 --- a/tests/test_stack.py +++ b/tests/test_stack.py @@ -191,7 +191,7 @@ def test_init__non_boolean_obsolete_value__raises_invalid_config_file_error(self @pytest.mark.parametrize( "parameters", [{"someNum": 1}, {"someBool": True}, {"aBadList": [1, 2, 3]}] ) - def test_init__invalid_types_raise_invalid_config_file_error(self, parameters): + def test_init__invalid_parameters_raise_invalid_config_file_error(self, parameters): with pytest.raises(InvalidConfigFileError): Stack( name="stack_name", @@ -210,7 +210,7 @@ def test_init__invalid_types_raise_invalid_config_file_error(self, parameters): {"aResolver": FakeResolver()}, ], ) - def test_init__valid_types(self, parameters): + def test_init__valid_parameters_do_not(self, parameters): stack = Stack( name="stack_name", project_code="project_code", From dc1c23db6ebd58cbeeef602ecc3b1dc3444a0fb7 Mon Sep 17 00:00:00 2001 From: Alex Harvey Date: Tue, 20 Feb 2024 17:33:58 +1100 Subject: [PATCH 11/11] add-test --- tests/test_stack.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/test_stack.py b/tests/test_stack.py index 0c1af37ab..6f67e08cd 100644 --- a/tests/test_stack.py +++ b/tests/test_stack.py @@ -189,7 +189,13 @@ def test_init__non_boolean_obsolete_value__raises_invalid_config_file_error(self ) @pytest.mark.parametrize( - "parameters", [{"someNum": 1}, {"someBool": True}, {"aBadList": [1, 2, 3]}] + "parameters", + [ + {"someNum": 1}, + {"someBool": True}, + {"aBadList": [1, 2, 3]}, + {"aDict": {"foo": "bar"}}, + ], ) def test_init__invalid_parameters_raise_invalid_config_file_error(self, parameters): with pytest.raises(InvalidConfigFileError): @@ -210,7 +216,9 @@ def test_init__invalid_parameters_raise_invalid_config_file_error(self, paramete {"aResolver": FakeResolver()}, ], ) - def test_init__valid_parameters_do_not(self, parameters): + def test_init__valid_parameters_do_not_raise_invalid_config_file_error( + self, parameters + ): stack = Stack( name="stack_name", project_code="project_code",