Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit a1684d2

Browse files
authored
CloudFormation v2 Engine: Support for Fn::Select (#12679)
1 parent df48f25 commit a1684d2

File tree

9 files changed

+2652
-2
lines changed

9 files changed

+2652
-2
lines changed

‎localstack-core/localstack/services/cloudformation/engine/v2/change_set_model.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,7 @@ def __init__(self, scope: Scope, value: Any):
423423
FnFindInMapKey: Final[str] = "Fn::FindInMap"
424424
FnSubKey: Final[str] = "Fn::Sub"
425425
FnTransform: Final[str] = "Fn::Transform"
426+
FnSelect: Final[str] = "Fn::Select"
426427
INTRINSIC_FUNCTIONS: Final[set[str]] = {
427428
RefKey,
428429
FnIfKey,
@@ -433,6 +434,7 @@ def __init__(self, scope: Scope, value: Any):
433434
FnFindInMapKey,
434435
FnSubKey,
435436
FnTransform,
437+
FnSelect,
436438
}
437439

438440

‎localstack-core/localstack/services/cloudformation/engine/v2/change_set_model_preproc.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -646,6 +646,35 @@ def _compute_join(args: list[Any]) -> str:
646646
after = _compute_join(arguments_after)
647647
return PreprocEntityDelta(before=before, after=after)
648648

649+
def visit_node_intrinsic_function_fn_select(
650+
self, node_intrinsic_function: NodeIntrinsicFunction
651+
):
652+
# TODO: add further support for schema validation
653+
arguments_delta = self.visit(node_intrinsic_function.arguments)
654+
arguments_before = arguments_delta.before
655+
arguments_after = arguments_delta.after
656+
657+
def _compute_fn_select(args: list[Any]) -> Any:
658+
values: list[Any] = args[1]
659+
if not isinstance(values, list) or not values:
660+
raise RuntimeError(f"Invalid arguments list value for Fn::Select: '{values}'")
661+
values_len = len(values)
662+
index: int = int(args[0])
663+
if not isinstance(index, int) or index < 0 or index > values_len:
664+
raise RuntimeError(f"Invalid or out of range index value for Fn::Select: '{index}'")
665+
selection = values[index]
666+
return selection
667+
668+
before = Nothing
669+
if not is_nothing(arguments_before):
670+
before = _compute_fn_select(arguments_before)
671+
672+
after = Nothing
673+
if not is_nothing(arguments_after):
674+
after = _compute_fn_select(arguments_after)
675+
676+
return PreprocEntityDelta(before=before, after=after)
677+
649678
def visit_node_intrinsic_function_fn_find_in_map(
650679
self, node_intrinsic_function: NodeIntrinsicFunction
651680
) -> PreprocEntityDelta:

‎localstack-core/localstack/services/cloudformation/engine/v2/change_set_model_visitor.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,11 @@ def visit_node_intrinsic_function_fn_transform(
118118
):
119119
self.visit_children(node_intrinsic_function)
120120

121+
def visit_node_intrinsic_function_fn_select(
122+
self, node_intrinsic_function: NodeIntrinsicFunction
123+
):
124+
self.visit_children(node_intrinsic_function)
125+
121126
def visit_node_intrinsic_function_fn_sub(self, node_intrinsic_function: NodeIntrinsicFunction):
122127
self.visit_children(node_intrinsic_function)
123128

‎tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -427,7 +427,6 @@ def test_conditional_in_conditional(self, env, region, deploy_cfn_template, aws_
427427
else:
428428
assert stack.outputs["Result"] == "false"
429429

430-
@pytest.mark.skip(reason="CFNV2:Fn::Select")
431430
@markers.aws.validated
432431
def test_conditional_with_select(self, deploy_cfn_template, aws_client):
433432
stack = deploy_cfn_template(

‎tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1202,7 +1202,6 @@ class TestCfnLambdaDestinations:
12021202
12031203
"""
12041204

1205-
@pytest.mark.skip(reason="CFNV2:Fn::Select")
12061205
@pytest.mark.parametrize(
12071206
["on_success", "on_failure"],
12081207
[

‎tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_stepfunctions.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ def _is_executed():
4646
assert "hello from statemachine" in execution_desc["output"]
4747

4848

49+
@pytest.mark.skip(reason="CFNV2:Fn::Split")
4950
@markers.aws.validated
5051
def test_nested_statemachine_with_sync2(deploy_cfn_template, aws_client):
5152
stack = deploy_cfn_template(
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
import pytest
2+
from localstack_snapshot.snapshots.transformer import RegexTransformer
3+
4+
from localstack.services.cloudformation.v2.utils import is_v2_engine
5+
from localstack.testing.aws.util import is_aws_cloud
6+
from localstack.testing.pytest import markers
7+
from localstack.utils.strings import long_uid
8+
9+
10+
@pytest.mark.skipif(
11+
condition=not is_v2_engine() and not is_aws_cloud(), reason="Requires the V2 engine"
12+
)
13+
@markers.snapshot.skip_snapshot_verify(
14+
paths=[
15+
"per-resource-events..*",
16+
"delete-describe..*",
17+
#
18+
# Before/After Context
19+
"$..Capabilities",
20+
"$..NotificationARNs",
21+
"$..IncludeNestedStacks",
22+
"$..Scope",
23+
"$..Details",
24+
"$..Parameters",
25+
"$..Replacement",
26+
"$..PolicyAction",
27+
]
28+
)
29+
class TestChangeSetFnSelect:
30+
@markers.aws.validated
31+
def test_fn_select_add_to_static_property(
32+
self,
33+
snapshot,
34+
capture_update_process,
35+
):
36+
name1 = f"topic-name-1-{long_uid()}"
37+
snapshot.add_transformer(RegexTransformer(name1, "topic-name-1"))
38+
template_1 = {
39+
"Resources": {
40+
"Topic1": {"Type": "AWS::SNS::Topic", "Properties": {"DisplayName": name1}}
41+
}
42+
}
43+
template_2 = {
44+
"Resources": {
45+
"Topic1": {
46+
"Type": "AWS::SNS::Topic",
47+
"Properties": {"DisplayName": {"Fn::Select": [1, ["1st", "2nd", "3rd"]]}},
48+
}
49+
}
50+
}
51+
capture_update_process(snapshot, template_1, template_2)
52+
53+
@markers.aws.validated
54+
def test_fn_select_remove_from_static_property(
55+
self,
56+
snapshot,
57+
capture_update_process,
58+
):
59+
name1 = f"topic-name-1-{long_uid()}"
60+
snapshot.add_transformer(RegexTransformer(name1, "topic-name-1"))
61+
template_1 = {
62+
"Resources": {
63+
"Topic1": {
64+
"Type": "AWS::SNS::Topic",
65+
"Properties": {"DisplayName": {"Fn::Select": [1, ["1st", "2nd", "3rd"]]}},
66+
}
67+
}
68+
}
69+
template_2 = {
70+
"Resources": {
71+
"Topic1": {"Type": "AWS::SNS::Topic", "Properties": {"DisplayName": name1}}
72+
}
73+
}
74+
capture_update_process(snapshot, template_1, template_2)
75+
76+
@markers.aws.validated
77+
def test_fn_select_change_in_selection_list(
78+
self,
79+
snapshot,
80+
capture_update_process,
81+
):
82+
name1 = f"topic-name-1-{long_uid()}"
83+
snapshot.add_transformer(RegexTransformer(name1, "topic-name-1"))
84+
template_1 = {
85+
"Resources": {
86+
"Topic1": {
87+
"Type": "AWS::SNS::Topic",
88+
"Properties": {"DisplayName": {"Fn::Select": [1, ["1st", "2nd"]]}},
89+
}
90+
}
91+
}
92+
template_2 = {
93+
"Resources": {
94+
"Topic1": {
95+
"Type": "AWS::SNS::Topic",
96+
"Properties": {"DisplayName": {"Fn::Select": [1, ["1st", "new-2nd", "3rd"]]}},
97+
}
98+
}
99+
}
100+
capture_update_process(snapshot, template_1, template_2)
101+
102+
@markers.aws.validated
103+
def test_fn_select_change_in_selection_index_only(
104+
self,
105+
snapshot,
106+
capture_update_process,
107+
):
108+
name1 = f"topic-name-1-{long_uid()}"
109+
snapshot.add_transformer(RegexTransformer(name1, "topic-name-1"))
110+
template_1 = {
111+
"Resources": {
112+
"Topic1": {
113+
"Type": "AWS::SNS::Topic",
114+
"Properties": {"DisplayName": {"Fn::Select": [1, ["1st", "2nd"]]}},
115+
}
116+
}
117+
}
118+
template_2 = {
119+
"Resources": {
120+
"Topic1": {
121+
"Type": "AWS::SNS::Topic",
122+
"Properties": {"DisplayName": {"Fn::Select": [0, ["1st", "2nd"]]}},
123+
}
124+
}
125+
}
126+
capture_update_process(snapshot, template_1, template_2)
127+
128+
@markers.aws.validated
129+
def test_fn_select_change_in_selected_element_type_ref(
130+
self,
131+
snapshot,
132+
capture_update_process,
133+
):
134+
name1 = f"topic-name-1-{long_uid()}"
135+
snapshot.add_transformer(RegexTransformer(name1, "topic-name-1"))
136+
template_1 = {
137+
"Resources": {
138+
"Topic1": {
139+
"Type": "AWS::SNS::Topic",
140+
"Properties": {"DisplayName": {"Fn::Select": [0, ["1st"]]}},
141+
}
142+
}
143+
}
144+
template_2 = {
145+
"Resources": {
146+
"Topic1": {
147+
"Type": "AWS::SNS::Topic",
148+
"Properties": {"DisplayName": {"Fn::Select": [0, [{"Ref": "AWS::StackName"}]]}},
149+
}
150+
}
151+
}
152+
capture_update_process(snapshot, template_1, template_2)
153+
154+
@markers.snapshot.skip_snapshot_verify(
155+
paths=[
156+
# Reason: AWS incorrectly does not list the second and third topic as
157+
# needing modifying, however it needs to
158+
"describe-change-set-2-prop-values..Changes",
159+
]
160+
)
161+
@markers.aws.validated
162+
def test_fn_select_change_get_att_reference(
163+
self,
164+
snapshot,
165+
capture_update_process,
166+
):
167+
name1 = f"topic-name-1-{long_uid()}"
168+
name2 = f"topic-name-2-{long_uid()}"
169+
snapshot.add_transformer(RegexTransformer(name1, "topic-name-1"))
170+
snapshot.add_transformer(RegexTransformer(name2, "topic-name-2"))
171+
template_1 = {
172+
"Resources": {
173+
"Topic1": {
174+
"Type": "AWS::SNS::Topic",
175+
"Properties": {"DisplayName": name1},
176+
},
177+
"Topic2": {
178+
"Type": "AWS::SNS::Topic",
179+
"Properties": {
180+
"DisplayName": {
181+
"Fn::Select": [0, [{"Fn::GetAtt": ["Topic1", "DisplayName"]}]]
182+
}
183+
},
184+
},
185+
}
186+
}
187+
template_2 = {
188+
"Resources": {
189+
"Topic1": {
190+
"Type": "AWS::SNS::Topic",
191+
"Properties": {"DisplayName": name2},
192+
},
193+
"Topic2": {
194+
"Type": "AWS::SNS::Topic",
195+
"Properties": {
196+
"DisplayName": {
197+
"Fn::Select": [0, [{"Fn::GetAtt": ["Topic1", "DisplayName"]}]]
198+
}
199+
},
200+
},
201+
}
202+
}
203+
capture_update_process(snapshot, template_1, template_2)

0 commit comments

Comments
 (0)