diff --git a/sceptre/stack.py b/sceptre/stack.py index fa5639a8b..f085ced7f 100644 --- a/sceptre/stack.py +++ b/sceptre/stack.py @@ -263,7 +263,7 @@ def __init__( ) self.s3_details = s3_details - self.parameters = self._ensure_parameters(parameters or {}) + self.parameters = self._cast_parameters(parameters or {}) self.sceptre_user_data = sceptre_user_data or {} self.notifications = notifications or [] @@ -276,10 +276,21 @@ def _ensure_boolean(self, config_name: str, value: Any) -> bool: ) return value - def _ensure_parameters( + def _cast_parameters( self, parameters: Dict[str, Any] ) -> Dict[str, Union[str, List[Union[str, Resolver]], Resolver]]: - """Ensure CloudFormation parameters are of valid types""" + """Cast CloudFormation parameters to valid types""" + + def cast_value(value: Any) -> Union[str, List[Union[str, Resolver]], Resolver]: + if isinstance(value, bool): + return "true" if value else "false" + elif isinstance(value, (int, float)): + return str(value) + elif isinstance(value, list): + return [cast_value(item) for item in value] + elif isinstance(value, Resolver): + return value + return value def is_valid(value: Any) -> bool: return ( @@ -294,11 +305,14 @@ def is_valid(value: Any) -> bool: or isinstance(value, Resolver) ) - if not all(is_valid(value) for value in parameters.values()): + casted_parameters = {k: cast_value(v) for k, v in parameters.items()} + + if not all(is_valid(value) for value in casted_parameters.values()): raise InvalidConfigFileError( - f"{self.name}: Values for parameters must be strings, lists or resolvers, got {parameters}" + f"{self.name}: Values for parameters must be strings, lists or resolvers, got {casted_parameters}" ) - return parameters + + return casted_parameters def __repr__(self): return ( diff --git a/tests/test_stack.py b/tests/test_stack.py index 6f67e08cd..eeddf7451 100644 --- a/tests/test_stack.py +++ b/tests/test_stack.py @@ -190,12 +190,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]}, - {"aDict": {"foo": "bar"}}, - ], + [{"Dict": {"foo": "bar"}}, {"List": ["of", "stuff", {"including": "aDict"}]}], ) def test_init__invalid_parameters_raise_invalid_config_file_error(self, parameters): with pytest.raises(InvalidConfigFileError): @@ -210,10 +205,13 @@ def test_init__invalid_parameters_raise_invalid_config_file_error(self, paramete @pytest.mark.parametrize( "parameters", [ - {"someNum": "1"}, - {"someBool": "true"}, - {"aList": ["aString", FakeResolver()]}, - {"aResolver": FakeResolver()}, + {"IntAsString": "1"}, + {"Int": 1}, + {"BoolAsString": "true"}, + {"Bool": True}, + {"List": ["aStringAndA", FakeResolver()]}, + {"ListOfInts": [1, 2, 3]}, + {"Resolver": FakeResolver()}, ], ) def test_init__valid_parameters_do_not_raise_invalid_config_file_error(