Skip to content

Commit

Permalink
Handle Fn::Transforms inside Mappings (#3419)
Browse files Browse the repository at this point in the history
  • Loading branch information
kddejong authored Jun 26, 2024
1 parent 3035a52 commit c02a5ed
Show file tree
Hide file tree
Showing 4 changed files with 39 additions and 2 deletions.
10 changes: 9 additions & 1 deletion src/cfnlint/context/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -384,11 +384,15 @@ class _MappingSecondaryKey:
init=False, default_factory=dict
)
instance: InitVar[Any]
is_transform: bool = field(init=False, default=False)

def __post_init__(self, instance) -> None:
if not isinstance(instance, dict):
raise ValueError("Secondary keys must be a object")
for k, v in instance.items():
if k == "Fn::Transform":
self.is_transform = True
continue
if isinstance(v, (str, list, int, float)):
self.keys[k] = v
else:
Expand All @@ -408,12 +412,16 @@ class Map:

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

def __post_init__(self, mapping) -> None:
if not isinstance(mapping, dict):
raise ValueError("Mapping must be a object")
for k, v in mapping.items():
self.keys[k] = _MappingSecondaryKey(v)
if k == "Fn::Transform":
self.is_transform = True
else:
self.keys[k] = _MappingSecondaryKey(v)

def find_in_map(self, top_key: str, secondary_key: str) -> Iterator[Any]:
if top_key not in self.keys:
Expand Down
4 changes: 4 additions & 0 deletions src/cfnlint/jsonschema/_resolvers_cfn.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ def find_in_map(validator: Validator, instance: Any) -> ResolutionResult:

top_key = map.keys.get(instance[1])
if top_key is None:
if map.is_transform:
return
if not default_value_found:
yield None, validator, ValidationError(
(
Expand All @@ -98,6 +100,8 @@ def find_in_map(validator: Validator, instance: Any) -> ResolutionResult:

value = top_key.keys.get(instance[2])
if value is None:
if top_key.is_transform:
return
if not default_value_found:
yield value, validator, ValidationError(
(
Expand Down
10 changes: 10 additions & 0 deletions test/unit/module/context/test_mappings.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,13 @@ def test_mapping_value(name, get_key_1, get_key_2, expected):
list(mapping.find_in_map(get_key_1, get_key_2))
else:
assert list(mapping.find_in_map(get_key_1, get_key_2)) == expected


def test_transforms():
mapping = Map({"A": {"Fn::Transform": "C"}})

assert mapping.keys.get("A").is_transform is True

mapping = Map({"Fn::Transform": {"B": "C"}})

assert mapping.is_transform is True
17 changes: 16 additions & 1 deletion test/unit/module/jsonschema/test_resolvers_cfn.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,10 @@ def test_invalid_functions(name, instance, response):
None,
deque([]),
ValidationError(
("'bar' is not one of ['foo']"),
(
"'bar' is not one of ['foo', "
"'transformFirstKey', 'transformSecondKey']"
),
path=deque(["Fn::FindInMap", 0]),
),
)
Expand Down Expand Up @@ -304,6 +307,16 @@ def test_invalid_functions(name, instance, response):
{"Fn::FindInMap": ["foo", "first", "third", {"DefaultValue": "default"}]},
[("default", deque([4, "DefaultValue"]), None)],
),
(
"Valid FindInMap with a transform on first key",
{"Fn::FindInMap": ["transformFirstKey", "first", "third"]},
[],
),
(
"Valid FindInMap with a transform on second key",
{"Fn::FindInMap": ["transformSecondKey", "first", "third"]},
[],
),
(
"Valid Sub with a resolvable values",
{"Fn::Sub": ["${a}-${b}", {"a": "foo", "b": "bar"}]},
Expand All @@ -319,6 +332,8 @@ def test_invalid_functions(name, instance, response):
def test_valid_functions(name, instance, response):
context = Context()
context.mappings["foo"] = Map({"first": {"second": "bar"}})
context.mappings["transformFirstKey"] = Map({"Fn::Transform": {"second": "bar"}})
context.mappings["transformSecondKey"] = Map({"first": {"Fn::Transform": "bar"}})

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

Expand Down

0 comments on commit c02a5ed

Please sign in to comment.