Skip to content

Commit

Permalink
Merge branch 'master' into 1187-configurable-inheritance-strategies
Browse files Browse the repository at this point in the history
  • Loading branch information
zaro0508 committed Mar 20, 2024
2 parents 32f486c + a58f3e9 commit b73f287
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 14 deletions.
6 changes: 3 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ repos:
hooks:
- id: flake8
- repo: https://github.com/adrienverge/yamllint
rev: v1.34.0
rev: v1.35.1
hooks:
- id: yamllint
- repo: https://github.com/awslabs/cfn-python-lint
rev: v0.85.1
rev: v0.86.0
hooks:
- id: cfn-python-lint
args:
Expand All @@ -40,7 +40,7 @@ repos:
hooks:
- id: black
- repo: https://github.com/python-poetry/poetry
rev: '1.7.0'
rev: '1.8.0'
hooks:
- id: poetry-check
- id: poetry-lock
Expand Down
12 changes: 8 additions & 4 deletions sceptre/config/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,14 +90,18 @@ def _generate_edges(self, stack: Stack, dependencies: List[Stack]):
:param stack: A Sceptre Stack
:param dependencies: a collection of dependency paths
"""
self.logger.debug("Generate dependencies for stack {0}".format(stack))
self.logger.debug(f"Generate dependencies for stack {stack}")
for dependency in set(dependencies):
self.graph.add_edge(dependency, stack)
if not nx.is_directed_acyclic_graph(self.graph):
try:
cycle = nx.find_cycle(self.graph, orientation="original")
cycle_str = ", ".join([f"{edge[0]} -> {edge[1]}" for edge in cycle])
raise CircularDependenciesError(
f"Dependency cycle detected: {stack} {dependency}"
f"Dependency cycle detected: {cycle_str}"
)
self.logger.debug(" Added dependency: {}".format(dependency))
except nx.NetworkXNoCycle:
pass # No cycle, continue
self.logger.debug(f" Added dependency: {dependency}")

if not dependencies:
self.graph.add_node(stack)
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

0 comments on commit b73f287

Please sign in to comment.