Skip to content

Commit c02a5ed

Browse files
authored
Handle Fn::Transforms inside Mappings (#3419)
1 parent 3035a52 commit c02a5ed

File tree

4 files changed

+39
-2
lines changed

4 files changed

+39
-2
lines changed

src/cfnlint/context/context.py

+9-1
Original file line numberDiff line numberDiff line change
@@ -384,11 +384,15 @@ class _MappingSecondaryKey:
384384
init=False, default_factory=dict
385385
)
386386
instance: InitVar[Any]
387+
is_transform: bool = field(init=False, default=False)
387388

388389
def __post_init__(self, instance) -> None:
389390
if not isinstance(instance, dict):
390391
raise ValueError("Secondary keys must be a object")
391392
for k, v in instance.items():
393+
if k == "Fn::Transform":
394+
self.is_transform = True
395+
continue
392396
if isinstance(v, (str, list, int, float)):
393397
self.keys[k] = v
394398
else:
@@ -408,12 +412,16 @@ class Map:
408412

409413
keys: dict[str, _MappingSecondaryKey] = field(init=False, default_factory=dict)
410414
resource: InitVar[Any]
415+
is_transform: bool = field(init=False, default=False)
411416

412417
def __post_init__(self, mapping) -> None:
413418
if not isinstance(mapping, dict):
414419
raise ValueError("Mapping must be a object")
415420
for k, v in mapping.items():
416-
self.keys[k] = _MappingSecondaryKey(v)
421+
if k == "Fn::Transform":
422+
self.is_transform = True
423+
else:
424+
self.keys[k] = _MappingSecondaryKey(v)
417425

418426
def find_in_map(self, top_key: str, secondary_key: str) -> Iterator[Any]:
419427
if top_key not in self.keys:

src/cfnlint/jsonschema/_resolvers_cfn.py

+4
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ def find_in_map(validator: Validator, instance: Any) -> ResolutionResult:
8585

8686
top_key = map.keys.get(instance[1])
8787
if top_key is None:
88+
if map.is_transform:
89+
return
8890
if not default_value_found:
8991
yield None, validator, ValidationError(
9092
(
@@ -98,6 +100,8 @@ def find_in_map(validator: Validator, instance: Any) -> ResolutionResult:
98100

99101
value = top_key.keys.get(instance[2])
100102
if value is None:
103+
if top_key.is_transform:
104+
return
101105
if not default_value_found:
102106
yield value, validator, ValidationError(
103107
(

test/unit/module/context/test_mappings.py

+10
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,13 @@ def test_mapping_value(name, get_key_1, get_key_2, expected):
2424
list(mapping.find_in_map(get_key_1, get_key_2))
2525
else:
2626
assert list(mapping.find_in_map(get_key_1, get_key_2)) == expected
27+
28+
29+
def test_transforms():
30+
mapping = Map({"A": {"Fn::Transform": "C"}})
31+
32+
assert mapping.keys.get("A").is_transform is True
33+
34+
mapping = Map({"Fn::Transform": {"B": "C"}})
35+
36+
assert mapping.is_transform is True

test/unit/module/jsonschema/test_resolvers_cfn.py

+16-1
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,10 @@ def test_invalid_functions(name, instance, response):
240240
None,
241241
deque([]),
242242
ValidationError(
243-
("'bar' is not one of ['foo']"),
243+
(
244+
"'bar' is not one of ['foo', "
245+
"'transformFirstKey', 'transformSecondKey']"
246+
),
244247
path=deque(["Fn::FindInMap", 0]),
245248
),
246249
)
@@ -304,6 +307,16 @@ def test_invalid_functions(name, instance, response):
304307
{"Fn::FindInMap": ["foo", "first", "third", {"DefaultValue": "default"}]},
305308
[("default", deque([4, "DefaultValue"]), None)],
306309
),
310+
(
311+
"Valid FindInMap with a transform on first key",
312+
{"Fn::FindInMap": ["transformFirstKey", "first", "third"]},
313+
[],
314+
),
315+
(
316+
"Valid FindInMap with a transform on second key",
317+
{"Fn::FindInMap": ["transformSecondKey", "first", "third"]},
318+
[],
319+
),
307320
(
308321
"Valid Sub with a resolvable values",
309322
{"Fn::Sub": ["${a}-${b}", {"a": "foo", "b": "bar"}]},
@@ -319,6 +332,8 @@ def test_invalid_functions(name, instance, response):
319332
def test_valid_functions(name, instance, response):
320333
context = Context()
321334
context.mappings["foo"] = Map({"first": {"second": "bar"}})
335+
context.mappings["transformFirstKey"] = Map({"Fn::Transform": {"second": "bar"}})
336+
context.mappings["transformSecondKey"] = Map({"first": {"Fn::Transform": "bar"}})
322337

323338
_resolve(name, instance, response, context=context)
324339

0 commit comments

Comments
 (0)