Skip to content

Commit

Permalink
Update findinmap resolv to handle Ref psedueparams (#3785)
Browse files Browse the repository at this point in the history
* Update findinmap resolv to handle Ref psedueparams
  • Loading branch information
kddejong authored Oct 23, 2024
1 parent a6eb97a commit 74847b1
Show file tree
Hide file tree
Showing 2 changed files with 237 additions and 8 deletions.
70 changes: 67 additions & 3 deletions src/cfnlint/jsonschema/_resolvers_cfn.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
AVAILABILITY_ZONES,
PSEUDOPARAMS,
REGEX_SUB_PARAMETERS,
REGIONS,
is_function,
)
from cfnlint.jsonschema import ValidationError, Validator
Expand Down Expand Up @@ -98,9 +99,75 @@ def find_in_map(validator: Validator, instance: Any) -> ResolutionResult:
if validator.context.mappings.maps[map_name].is_transform:
continue

k, v = is_function(instance[2])
if k == "Ref" and v in PSEUDOPARAMS:
continue

k, v = is_function(instance[1])
if k == "Ref" and v in PSEUDOPARAMS:
if isinstance(instance[2], str):
found_top_level_key = False
found_second_key = False
for top_level_key, top_values in validator.context.mappings.maps[
map_name
].keys.items():
if v == "AWS::AccountId":
if not re.match("^[0-9]{12}$", top_level_key):
continue
elif v == "AWS::Region":
if top_level_key not in REGIONS:
continue
found_top_level_key = True
for second_level_key, second_v, _ in validator.resolve_value(
instance[2]
):
if second_level_key in top_values.keys:
for value in validator.context.mappings.maps[
map_name
].find_in_map(
top_level_key,
second_level_key,
):
found_second_key = True
yield (
value,
validator.evolve(
context=validator.context.evolve(
path=validator.context.path.evolve(
value_path=deque(
[
"Mappings",
map_name,
top_level_key,
second_level_key,
]
)
)
)
),
None,
)

if not found_top_level_key:
yield None, validator, ValidationError(
(
f"{instance[1]!r} is not a "
f"first level key for mapping {map_name!r}"
),
path=deque([1]),
)
elif not found_second_key:
yield None, validator, ValidationError(
(
f"{instance[2]!r} is not a "
"second level key when "
f"{instance[1]!r} is resolved "
f"for mapping {map_name!r}"
),
path=deque([2]),
)
continue

for top_level_key, top_v, _ in validator.resolve_value(instance[1]):
if validator.is_type(top_level_key, "integer"):
top_level_key = str(top_level_key)
Expand Down Expand Up @@ -134,9 +201,6 @@ def find_in_map(validator: Validator, instance: Any) -> ResolutionResult:
):
continue

k, v = is_function(instance[2])
if k == "Ref" and v in PSEUDOPARAMS:
continue
for second_level_key, second_v, err in validator.resolve_value(instance[2]):
if validator.is_type(second_level_key, "integer"):
second_level_key = str(second_level_key)
Expand Down
175 changes: 170 additions & 5 deletions test/unit/module/jsonschema/test_resolvers_cfn.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import pytest

from cfnlint.context._mappings import Mappings
from cfnlint.context.context import Context
from cfnlint.context.context import Context, Parameter
from cfnlint.jsonschema import ValidationError
from cfnlint.jsonschema.validators import CfnTemplateValidator

Expand Down Expand Up @@ -247,7 +247,7 @@ def test_invalid_functions(name, instance, response):
(
"'bar' is not one of ['foo', "
"'transformFirstKey', 'transformSecondKey', "
"'integers']"
"'integers', 'accounts', 'environments']"
),
path=deque(["Fn::FindInMap", 0]),
),
Expand Down Expand Up @@ -296,9 +296,75 @@ def test_invalid_functions(name, instance, response):
[],
),
(
"Valid FindInMap with an top level key that is a Ref to pseudo param",
"Valid FindInMap with an top level key that is a Ref to an account",
{"Fn::FindInMap": ["accounts", {"Ref": "AWS::AccountId"}, "dev"]},
[
(
"bar",
deque(["Mappings", "accounts", "123456789012", "dev"]),
None,
)
],
),
(
(
"Valid FindInMap with an top level key "
"that is a Ref to non account non region"
),
{"Fn::FindInMap": ["accounts", {"Ref": "AWS::StackName"}, "dev"]},
[
(
"bar",
deque(["Mappings", "accounts", "123456789012", "dev"]),
None,
)
],
),
(
"Valid FindInMap with an top level key that is a Ref to a region",
{"Fn::FindInMap": ["accounts", {"Ref": "AWS::Region"}, "bar"]},
[
(
"foo",
deque(["Mappings", "accounts", "us-east-1", "bar"]),
None,
)
],
),
(
"Invalid FindInMap with an top level key that is a Ref to an account",
{"Fn::FindInMap": ["accounts", {"Ref": "AWS::AccountId"}, "bar"]},
[
(
None,
deque([]),
ValidationError(
(
"'bar' is not a second level key "
"when {'Ref': 'AWS::AccountId'} is "
"resolved for mapping 'accounts'"
),
path=deque(["Fn::FindInMap", 2]),
),
)
],
),
(
"Invalid FindInMap with an top level key that is a Ref to pseudo param",
{"Fn::FindInMap": ["foo", {"Ref": "AWS::AccountId"}, "second"]},
[],
[
(
None,
deque([]),
ValidationError(
(
"{'Ref': 'AWS::AccountId'} is not a "
"first level key for mapping 'foo'"
),
path=deque(["Fn::FindInMap", 1]),
),
)
],
),
(
"Valid FindInMap with a second level key that is a Ref to pseudo param",
Expand Down Expand Up @@ -327,6 +393,78 @@ def test_invalid_functions(name, instance, response):
{"Fn::FindInMap": ["integers", 1, 2]},
[("Value", deque(["Mappings", "integers", "1", "2"]), None)],
),
(
(
"Valid FindInMap with a Ref to a parameter "
"with allowed values for top level key"
),
{"Fn::FindInMap": ["environments", {"Ref": "Environment"}, "foo"]},
[
("one", deque(["Mappings", "environments", "dev", "foo"]), None),
("two", deque(["Mappings", "environments", "test", "foo"]), None),
],
),
(
"Valid FindInMap with a Ref to a parameter for top level key",
{"Fn::FindInMap": ["environments", {"Ref": "RandomString"}, "foo"]},
[],
),
(
(
"Valid FindInMap with a Ref to accounts for top level "
"key Ref to a ramom string for second level key"
),
{
"Fn::FindInMap": [
"accounts",
{"Ref": "AWS::AccountId"},
{"Ref": "RandomString"},
]
},
[],
),
(
(
"Valid FindInMap with a Ref to accounts for top level "
"key Ref to an allowed value parameter for second level key"
),
{
"Fn::FindInMap": [
"accounts",
{"Ref": "AWS::AccountId"},
{"Ref": "Environment"},
]
},
[],
),
(
(
"Valid FindInMap with a Ref to a parameter "
"with allowed values for second level key"
),
{"Fn::FindInMap": ["environments", "lion", {"Ref": "Environment"}]},
[
("one", deque(["Mappings", "environments", "lion", "dev"]), None),
("two", deque(["Mappings", "environments", "lion", "test"]), None),
("three", deque(["Mappings", "environments", "lion", "prod"]), None),
],
),
(
"Valid FindInMap with a Ref to a parameter for top level key",
{"Fn::FindInMap": ["environments", {"Ref": "RandomString"}, "foo"]},
[],
),
(
"Valid FindInMap with a Ref to a parameter and a Ref to pseudo parameter",
{
"Fn::FindInMap": [
"accounts",
{"Ref": "AWS::AccountId"},
{"Ref": "RandomString"},
]
},
[],
),
(
"Valid FindInMap with a bad second key and default",
{"Fn::FindInMap": ["foo", "first", "third", {"DefaultValue": "default"}]},
Expand Down Expand Up @@ -356,14 +494,41 @@ def test_invalid_functions(name, instance, response):
)
def test_valid_functions(name, instance, response):
context = Context(
parameters={
"RandomString": Parameter(
{
"Type": "String",
}
),
"Environment": Parameter(
{
"Type": "String",
"AllowedValues": ["dev", "test", "prod"],
}
),
},
mappings=Mappings.create_from_dict(
{
"foo": {"first": {"second": "bar"}},
"transformFirstKey": {"Fn::Transform": {"second": "bar"}},
"transformSecondKey": {"first": {"Fn::Transform": "bar"}},
"integers": {"1": {"2": "Value"}},
"accounts": {
"123456789012": {"dev": "bar"},
"us-east-1": {"bar": "foo"},
},
"environments": {
"dev": {"foo": "one"},
"test": {"foo": "two"},
"prod": {"bar": "three"},
"lion": {
"dev": "one",
"test": "two",
"prod": "three",
},
},
}
)
),
)
_resolve(name, instance, response, context=context)

Expand Down

0 comments on commit 74847b1

Please sign in to comment.