Skip to content

Commit 74847b1

Browse files
authored
Update findinmap resolv to handle Ref psedueparams (#3785)
* Update findinmap resolv to handle Ref psedueparams
1 parent a6eb97a commit 74847b1

File tree

2 files changed

+237
-8
lines changed

2 files changed

+237
-8
lines changed

src/cfnlint/jsonschema/_resolvers_cfn.py

+67-3
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
AVAILABILITY_ZONES,
1616
PSEUDOPARAMS,
1717
REGEX_SUB_PARAMETERS,
18+
REGIONS,
1819
is_function,
1920
)
2021
from cfnlint.jsonschema import ValidationError, Validator
@@ -98,9 +99,75 @@ def find_in_map(validator: Validator, instance: Any) -> ResolutionResult:
9899
if validator.context.mappings.maps[map_name].is_transform:
99100
continue
100101

102+
k, v = is_function(instance[2])
103+
if k == "Ref" and v in PSEUDOPARAMS:
104+
continue
105+
101106
k, v = is_function(instance[1])
102107
if k == "Ref" and v in PSEUDOPARAMS:
108+
if isinstance(instance[2], str):
109+
found_top_level_key = False
110+
found_second_key = False
111+
for top_level_key, top_values in validator.context.mappings.maps[
112+
map_name
113+
].keys.items():
114+
if v == "AWS::AccountId":
115+
if not re.match("^[0-9]{12}$", top_level_key):
116+
continue
117+
elif v == "AWS::Region":
118+
if top_level_key not in REGIONS:
119+
continue
120+
found_top_level_key = True
121+
for second_level_key, second_v, _ in validator.resolve_value(
122+
instance[2]
123+
):
124+
if second_level_key in top_values.keys:
125+
for value in validator.context.mappings.maps[
126+
map_name
127+
].find_in_map(
128+
top_level_key,
129+
second_level_key,
130+
):
131+
found_second_key = True
132+
yield (
133+
value,
134+
validator.evolve(
135+
context=validator.context.evolve(
136+
path=validator.context.path.evolve(
137+
value_path=deque(
138+
[
139+
"Mappings",
140+
map_name,
141+
top_level_key,
142+
second_level_key,
143+
]
144+
)
145+
)
146+
)
147+
),
148+
None,
149+
)
150+
151+
if not found_top_level_key:
152+
yield None, validator, ValidationError(
153+
(
154+
f"{instance[1]!r} is not a "
155+
f"first level key for mapping {map_name!r}"
156+
),
157+
path=deque([1]),
158+
)
159+
elif not found_second_key:
160+
yield None, validator, ValidationError(
161+
(
162+
f"{instance[2]!r} is not a "
163+
"second level key when "
164+
f"{instance[1]!r} is resolved "
165+
f"for mapping {map_name!r}"
166+
),
167+
path=deque([2]),
168+
)
103169
continue
170+
104171
for top_level_key, top_v, _ in validator.resolve_value(instance[1]):
105172
if validator.is_type(top_level_key, "integer"):
106173
top_level_key = str(top_level_key)
@@ -134,9 +201,6 @@ def find_in_map(validator: Validator, instance: Any) -> ResolutionResult:
134201
):
135202
continue
136203

137-
k, v = is_function(instance[2])
138-
if k == "Ref" and v in PSEUDOPARAMS:
139-
continue
140204
for second_level_key, second_v, err in validator.resolve_value(instance[2]):
141205
if validator.is_type(second_level_key, "integer"):
142206
second_level_key = str(second_level_key)

test/unit/module/jsonschema/test_resolvers_cfn.py

+170-5
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import pytest
99

1010
from cfnlint.context._mappings import Mappings
11-
from cfnlint.context.context import Context
11+
from cfnlint.context.context import Context, Parameter
1212
from cfnlint.jsonschema import ValidationError
1313
from cfnlint.jsonschema.validators import CfnTemplateValidator
1414

@@ -247,7 +247,7 @@ def test_invalid_functions(name, instance, response):
247247
(
248248
"'bar' is not one of ['foo', "
249249
"'transformFirstKey', 'transformSecondKey', "
250-
"'integers']"
250+
"'integers', 'accounts', 'environments']"
251251
),
252252
path=deque(["Fn::FindInMap", 0]),
253253
),
@@ -296,9 +296,75 @@ def test_invalid_functions(name, instance, response):
296296
[],
297297
),
298298
(
299-
"Valid FindInMap with an top level key that is a Ref to pseudo param",
299+
"Valid FindInMap with an top level key that is a Ref to an account",
300+
{"Fn::FindInMap": ["accounts", {"Ref": "AWS::AccountId"}, "dev"]},
301+
[
302+
(
303+
"bar",
304+
deque(["Mappings", "accounts", "123456789012", "dev"]),
305+
None,
306+
)
307+
],
308+
),
309+
(
310+
(
311+
"Valid FindInMap with an top level key "
312+
"that is a Ref to non account non region"
313+
),
314+
{"Fn::FindInMap": ["accounts", {"Ref": "AWS::StackName"}, "dev"]},
315+
[
316+
(
317+
"bar",
318+
deque(["Mappings", "accounts", "123456789012", "dev"]),
319+
None,
320+
)
321+
],
322+
),
323+
(
324+
"Valid FindInMap with an top level key that is a Ref to a region",
325+
{"Fn::FindInMap": ["accounts", {"Ref": "AWS::Region"}, "bar"]},
326+
[
327+
(
328+
"foo",
329+
deque(["Mappings", "accounts", "us-east-1", "bar"]),
330+
None,
331+
)
332+
],
333+
),
334+
(
335+
"Invalid FindInMap with an top level key that is a Ref to an account",
336+
{"Fn::FindInMap": ["accounts", {"Ref": "AWS::AccountId"}, "bar"]},
337+
[
338+
(
339+
None,
340+
deque([]),
341+
ValidationError(
342+
(
343+
"'bar' is not a second level key "
344+
"when {'Ref': 'AWS::AccountId'} is "
345+
"resolved for mapping 'accounts'"
346+
),
347+
path=deque(["Fn::FindInMap", 2]),
348+
),
349+
)
350+
],
351+
),
352+
(
353+
"Invalid FindInMap with an top level key that is a Ref to pseudo param",
300354
{"Fn::FindInMap": ["foo", {"Ref": "AWS::AccountId"}, "second"]},
301-
[],
355+
[
356+
(
357+
None,
358+
deque([]),
359+
ValidationError(
360+
(
361+
"{'Ref': 'AWS::AccountId'} is not a "
362+
"first level key for mapping 'foo'"
363+
),
364+
path=deque(["Fn::FindInMap", 1]),
365+
),
366+
)
367+
],
302368
),
303369
(
304370
"Valid FindInMap with a second level key that is a Ref to pseudo param",
@@ -327,6 +393,78 @@ def test_invalid_functions(name, instance, response):
327393
{"Fn::FindInMap": ["integers", 1, 2]},
328394
[("Value", deque(["Mappings", "integers", "1", "2"]), None)],
329395
),
396+
(
397+
(
398+
"Valid FindInMap with a Ref to a parameter "
399+
"with allowed values for top level key"
400+
),
401+
{"Fn::FindInMap": ["environments", {"Ref": "Environment"}, "foo"]},
402+
[
403+
("one", deque(["Mappings", "environments", "dev", "foo"]), None),
404+
("two", deque(["Mappings", "environments", "test", "foo"]), None),
405+
],
406+
),
407+
(
408+
"Valid FindInMap with a Ref to a parameter for top level key",
409+
{"Fn::FindInMap": ["environments", {"Ref": "RandomString"}, "foo"]},
410+
[],
411+
),
412+
(
413+
(
414+
"Valid FindInMap with a Ref to accounts for top level "
415+
"key Ref to a ramom string for second level key"
416+
),
417+
{
418+
"Fn::FindInMap": [
419+
"accounts",
420+
{"Ref": "AWS::AccountId"},
421+
{"Ref": "RandomString"},
422+
]
423+
},
424+
[],
425+
),
426+
(
427+
(
428+
"Valid FindInMap with a Ref to accounts for top level "
429+
"key Ref to an allowed value parameter for second level key"
430+
),
431+
{
432+
"Fn::FindInMap": [
433+
"accounts",
434+
{"Ref": "AWS::AccountId"},
435+
{"Ref": "Environment"},
436+
]
437+
},
438+
[],
439+
),
440+
(
441+
(
442+
"Valid FindInMap with a Ref to a parameter "
443+
"with allowed values for second level key"
444+
),
445+
{"Fn::FindInMap": ["environments", "lion", {"Ref": "Environment"}]},
446+
[
447+
("one", deque(["Mappings", "environments", "lion", "dev"]), None),
448+
("two", deque(["Mappings", "environments", "lion", "test"]), None),
449+
("three", deque(["Mappings", "environments", "lion", "prod"]), None),
450+
],
451+
),
452+
(
453+
"Valid FindInMap with a Ref to a parameter for top level key",
454+
{"Fn::FindInMap": ["environments", {"Ref": "RandomString"}, "foo"]},
455+
[],
456+
),
457+
(
458+
"Valid FindInMap with a Ref to a parameter and a Ref to pseudo parameter",
459+
{
460+
"Fn::FindInMap": [
461+
"accounts",
462+
{"Ref": "AWS::AccountId"},
463+
{"Ref": "RandomString"},
464+
]
465+
},
466+
[],
467+
),
330468
(
331469
"Valid FindInMap with a bad second key and default",
332470
{"Fn::FindInMap": ["foo", "first", "third", {"DefaultValue": "default"}]},
@@ -356,14 +494,41 @@ def test_invalid_functions(name, instance, response):
356494
)
357495
def test_valid_functions(name, instance, response):
358496
context = Context(
497+
parameters={
498+
"RandomString": Parameter(
499+
{
500+
"Type": "String",
501+
}
502+
),
503+
"Environment": Parameter(
504+
{
505+
"Type": "String",
506+
"AllowedValues": ["dev", "test", "prod"],
507+
}
508+
),
509+
},
359510
mappings=Mappings.create_from_dict(
360511
{
361512
"foo": {"first": {"second": "bar"}},
362513
"transformFirstKey": {"Fn::Transform": {"second": "bar"}},
363514
"transformSecondKey": {"first": {"Fn::Transform": "bar"}},
364515
"integers": {"1": {"2": "Value"}},
516+
"accounts": {
517+
"123456789012": {"dev": "bar"},
518+
"us-east-1": {"bar": "foo"},
519+
},
520+
"environments": {
521+
"dev": {"foo": "one"},
522+
"test": {"foo": "two"},
523+
"prod": {"bar": "three"},
524+
"lion": {
525+
"dev": "one",
526+
"test": "two",
527+
"prod": "three",
528+
},
529+
},
365530
}
366-
)
531+
),
367532
)
368533
_resolve(name, instance, response, context=context)
369534

0 commit comments

Comments
 (0)