Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Resolve #1443] Add parameter type checking #1442

Merged
merged 11 commits into from
Feb 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 27 additions & 2 deletions sceptre/stack.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

import logging

from typing import List, Any, Optional
from typing import List, Dict, Union, Any, Optional
from deprecation import deprecated

from sceptre import __version__
Expand All @@ -26,6 +26,7 @@
ResolvableValueProperty,
RecursiveResolve,
PlaceholderType,
Resolver,
)
from sceptre.template import Template

Expand Down Expand Up @@ -262,7 +263,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 []

Expand All @@ -275,6 +276,30 @@ def _ensure_boolean(self, config_name: str, value: Any) -> bool:
)
return value

def _ensure_parameters(
self, parameters: Dict[str, Any]
) -> Dict[str, Union[str, List[Union[str, Resolver]], 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) or isinstance(item, Resolver)
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, lists or resolvers, got {parameters}"
)
return parameters

def __repr__(self):
return (
"sceptre.stack.Stack("
Expand Down
51 changes: 46 additions & 5 deletions tests/test_stack.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -183,6 +188,46 @@ 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]},
{"aDict": {"foo": "bar"}},
],
)
def test_init__invalid_parameters_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"},
{"aList": ["aString", FakeResolver()]},
{"aResolver": FakeResolver()},
],
)
def test_init__valid_parameters_do_not_raise_invalid_config_file_error(
self, parameters
):
stack = Stack(
name="stack_name",
project_code="project_code",
template_handler_config={"type": "file"},
region="region",
parameters=parameters,
)
assert isinstance(stack, Stack)

def test_stack_repr(self):
assert (
self.stack.__repr__() == "sceptre.stack.Stack("
Expand Down Expand Up @@ -248,14 +293,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):
Expand Down