Skip to content

Commit 5271fc0

Browse files
authored
Fix: CFn: mappings with references (#11539)
1 parent b770a27 commit 5271fc0

File tree

5 files changed

+124
-1
lines changed

5 files changed

+124
-1
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
class TemplateError(RuntimeError):
2+
"""
3+
Error thrown on a programming error from the user
4+
"""

localstack-core/localstack/services/cloudformation/engine/template_utils.py

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from typing import Any
33

44
from localstack.services.cloudformation.deployment_utils import PLACEHOLDER_AWS_NO_VALUE
5+
from localstack.services.cloudformation.engine.errors import TemplateError
56
from localstack.utils.urls import localstack_host
67

78
AWS_URL_SUFFIX = localstack_host().host # value is "amazonaws.com" in real AWS
@@ -181,7 +182,52 @@ def resolve_condition(
181182
)
182183
case "Fn::FindInMap":
183184
map_name, top_level_key, second_level_key = v
184-
return mappings[map_name][top_level_key][second_level_key]
185+
if isinstance(map_name, dict) and "Ref" in map_name:
186+
ref_name = map_name["Ref"]
187+
param = parameters.get(ref_name)
188+
if not param:
189+
raise TemplateError(
190+
f"Invalid reference: '{ref_name}' does not exist in parameters: '{parameters}'"
191+
)
192+
map_name = param.get("ResolvedValue") or param.get("ParameterValue")
193+
194+
if isinstance(top_level_key, dict) and "Ref" in top_level_key:
195+
ref_name = top_level_key["Ref"]
196+
param = parameters.get(ref_name)
197+
if not param:
198+
raise TemplateError(
199+
f"Invalid reference: '{ref_name}' does not exist in parameters: '{parameters}'"
200+
)
201+
top_level_key = param.get("ResolvedValue") or param.get("ParameterValue")
202+
203+
if isinstance(second_level_key, dict) and "Ref" in second_level_key:
204+
ref_name = second_level_key["Ref"]
205+
param = parameters.get(ref_name)
206+
if not param:
207+
raise TemplateError(
208+
f"Invalid reference: '{ref_name}' does not exist in parameters: '{parameters}'"
209+
)
210+
second_level_key = param.get("ResolvedValue") or param.get("ParameterValue")
211+
212+
mapping = mappings.get(map_name)
213+
if not mapping:
214+
raise TemplateError(
215+
f"Invalid reference: '{map_name}' could not be found in the template mappings: '{list(mappings.keys())}'"
216+
)
217+
218+
top_level_map = mapping.get(top_level_key)
219+
if not top_level_map:
220+
raise TemplateError(
221+
f"Invalid reference: '{top_level_key}' could not be found in the '{map_name}' mapping: '{list(mapping.keys())}'"
222+
)
223+
224+
value = top_level_map.get(second_level_key)
225+
if not value:
226+
raise TemplateError(
227+
f"Invalid reference: '{second_level_key}' could not be found in the '{top_level_key}' mapping: '{top_level_map}'"
228+
)
229+
230+
return value
185231
case "Fn::If":
186232
if_condition_name, true_branch, false_branch = v
187233
if resolve_condition(

tests/aws/services/cloudformation/engine/test_mappings.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,3 +153,33 @@ def test_mapping_minimum_nesting_depth(self, aws_client, cleanups, snapshot):
153153
],
154154
)
155155
snapshot.match("mapping_minimum_level_exc", e.value.response)
156+
157+
@markers.aws.validated
158+
@pytest.mark.parametrize(
159+
"map_key,should_error",
160+
[
161+
("A", False),
162+
("B", True),
163+
],
164+
ids=["should-deploy", "should-not-deploy"],
165+
)
166+
def test_mapping_ref_map_key(self, deploy_cfn_template, aws_client, map_key, should_error):
167+
topic_name = f"topic-{short_uid()}"
168+
stack = deploy_cfn_template(
169+
template_path=os.path.join(
170+
THIS_DIR, "../../../templates/mappings/mapping-ref-map-key.yaml"
171+
),
172+
parameters={
173+
"MapName": "MyMap",
174+
"MapKey": map_key,
175+
"TopicName": topic_name,
176+
},
177+
)
178+
179+
topic_arn = stack.outputs.get("TopicArn")
180+
if should_error:
181+
assert topic_arn is None
182+
else:
183+
assert topic_arn is not None
184+
185+
aws_client.sns.get_topic_attributes(TopicArn=topic_arn)

tests/aws/services/cloudformation/engine/test_mappings.validation.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@
55
"tests/aws/services/cloudformation/engine/test_mappings.py::TestCloudFormationMappings::test_mapping_minimum_nesting_depth": {
66
"last_validated_date": "2023-06-12T14:47:25+00:00"
77
},
8+
"tests/aws/services/cloudformation/engine/test_mappings.py::TestCloudFormationMappings::test_mapping_ref_map_key[should-deploy]": {
9+
"last_validated_date": "2024-09-20T11:09:25+00:00"
10+
},
11+
"tests/aws/services/cloudformation/engine/test_mappings.py::TestCloudFormationMappings::test_mapping_ref_map_key[should-not-deploy]": {
12+
"last_validated_date": "2024-09-20T11:09:25+00:00"
13+
},
814
"tests/aws/services/cloudformation/engine/test_mappings.py::TestCloudFormationMappings::test_mapping_with_invalid_refs": {
915
"last_validated_date": "2023-06-12T14:47:24+00:00"
1016
},
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
Mappings:
2+
MyMap:
3+
A:
4+
value: "true"
5+
B:
6+
value: "false"
7+
8+
Conditions:
9+
MyCondition: !Equals
10+
- !FindInMap [ !Ref MapName, !Ref MapKey, value ]
11+
- "true"
12+
13+
Parameters:
14+
MapName:
15+
Type: String
16+
17+
MapKey:
18+
Type: String
19+
20+
TopicName:
21+
Type: String
22+
23+
Resources:
24+
MyTopic:
25+
Type: AWS::SNS::Topic
26+
Properties:
27+
TopicName: !Ref TopicName
28+
Condition: MyCondition
29+
30+
Dummy:
31+
Type: AWS::SNS::Topic
32+
33+
Outputs:
34+
TopicArn:
35+
Value: !Ref MyTopic
36+
Condition: MyCondition
37+

0 commit comments

Comments
 (0)