Skip to content

Commit e79baed

Browse files
authored
Skip findinmap resolution when hitting a Sub (#3856)
* Add a flag to context to not resolve pseudo parameters * Update findinmap logic to not resolve pseudo parameters
1 parent 19192ef commit e79baed

File tree

4 files changed

+99
-5
lines changed

4 files changed

+99
-5
lines changed

src/cfnlint/context/context.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ class Context:
161161

162162
# is the value a resolved value
163163
is_resolved_value: bool = field(init=True, default=False)
164+
resolve_pseudo_parameters: bool = field(init=True, default=True)
164165

165166
def evolve(self, **kwargs) -> "Context":
166167
"""
@@ -180,8 +181,11 @@ def evolve(self, **kwargs) -> "Context":
180181
return cls(**kwargs)
181182

182183
def ref_value(self, instance: str) -> Iterator[Tuple[str | list[str], "Context"]]:
183-
if instance in PSEUDOPARAMS and instance not in self.pseudo_parameters:
184-
return
184+
if instance in PSEUDOPARAMS:
185+
if not self.resolve_pseudo_parameters:
186+
return
187+
if instance not in self.pseudo_parameters:
188+
return
185189

186190
if instance in self.ref_values:
187191
yield self.ref_values[instance], self

src/cfnlint/jsonschema/_resolvers_cfn.py

+13-1
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,10 @@ def find_in_map(validator: Validator, instance: Any) -> ResolutionResult:
201201
):
202202
continue
203203

204-
for second_level_key, second_v, err in validator.resolve_value(instance[2]):
204+
top_v = validator.evolve(
205+
context=top_v.context.evolve(resolve_pseudo_parameters=False)
206+
)
207+
for second_level_key, second_v, err in top_v.resolve_value(instance[2]):
205208
if validator.is_type(second_level_key, "integer"):
206209
second_level_key = str(second_level_key)
207210
if not validator.is_type(second_level_key, "string"):
@@ -418,6 +421,15 @@ def sub(validator: Validator, instance: Any) -> ResolutionResult:
418421
if not validator.is_type(parameters, "object"):
419422
return
420423

424+
sub_parameters = REGEX_SUB_PARAMETERS.findall(string)
425+
for parameter in sub_parameters:
426+
if parameter in parameters:
427+
continue
428+
if "." in parameter:
429+
parameters[parameter] = {"Fn::GetAtt": parameter}
430+
else:
431+
parameters[parameter] = {"Ref": parameter}
432+
421433
for resolved_parameters in _sub_parameter_expansion(validator, parameters):
422434
resolved_validator = validator.evolve(
423435
context=validator.context.evolve(

src/cfnlint/jsonschema/validators.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,12 @@ def resolve_value(self, instance: Any) -> ResolutionResult:
172172
for r_value, r_validator, r_errs in self._resolve_fn(key, value): # type: ignore
173173
if not r_errs:
174174
try:
175-
for _, region_context in r_validator.context.ref_value(
175+
region_validator = r_validator.evolve(
176+
context=r_validator.context.evolve(
177+
resolve_pseudo_parameters=True
178+
)
179+
)
180+
for _, region_context in region_validator.context.ref_value(
176181
"AWS::Region"
177182
):
178183
if self.cfn.conditions.satisfiable(

test/unit/module/jsonschema/test_resolvers_cfn.py

+74-1
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, Parameter
11+
from cfnlint.context.context import Context, Parameter, Resource
1212
from cfnlint.jsonschema import ValidationError
1313
from cfnlint.jsonschema.validators import CfnTemplateValidator
1414

@@ -480,6 +480,59 @@ def test_invalid_functions(name, instance, response):
480480
{"Fn::FindInMap": ["transformSecondKey", "first", "third"]},
481481
[],
482482
),
483+
(
484+
"Valid FindInMap using a Sub",
485+
{
486+
"Fn::FindInMap": [
487+
"environments",
488+
"lion",
489+
{"Fn::Sub": "${AWS::AccountId}Extra"},
490+
]
491+
},
492+
[],
493+
),
494+
(
495+
("Valid FindInMap with a Sub with no parameters"),
496+
{"Fn::FindInMap": ["environments", "lion", {"Fn::Sub": "dev"}]},
497+
[
498+
("one", deque(["Mappings", "environments", "lion", "dev"]), None),
499+
],
500+
),
501+
(
502+
("Valid FindInMap with sub to a paremter"),
503+
{"Fn::FindInMap": ["environments", "lion", {"Fn::Sub": "${Environment}"}]},
504+
[
505+
("one", deque(["Mappings", "environments", "lion", "dev"]), None),
506+
("two", deque(["Mappings", "environments", "lion", "test"]), None),
507+
("three", deque(["Mappings", "environments", "lion", "prod"]), None),
508+
],
509+
),
510+
(
511+
("Valid FindInMap with sub list value to a paramter"),
512+
{
513+
"Fn::FindInMap": [
514+
"environments",
515+
"lion",
516+
{"Fn::Sub": ["${Environment}", {}]},
517+
]
518+
},
519+
[
520+
("one", deque(["Mappings", "environments", "lion", "dev"]), None),
521+
("two", deque(["Mappings", "environments", "lion", "test"]), None),
522+
("three", deque(["Mappings", "environments", "lion", "prod"]), None),
523+
],
524+
),
525+
(
526+
("Valid FindInMap with an invalid sub"),
527+
{
528+
"Fn::FindInMap": [
529+
"environments",
530+
"lion",
531+
{"Fn::Sub": {"A": "B", "C": "D"}},
532+
]
533+
},
534+
[],
535+
),
483536
(
484537
"Valid Sub with a resolvable values",
485538
{"Fn::Sub": ["${a}-${b}", {"a": "foo", "b": "bar"}]},
@@ -490,6 +543,16 @@ def test_invalid_functions(name, instance, response):
490543
{"Fn::Sub": ["foo", {}]},
491544
[("foo", deque([]), None)],
492545
),
546+
(
547+
"Valid Sub with a getatt and list",
548+
{"Fn::Sub": ["${MyResource.Arn}", {}]},
549+
[],
550+
),
551+
(
552+
"Valid Sub with a getatt string",
553+
{"Fn::Sub": "${MyResource.Arn}"},
554+
[],
555+
),
493556
],
494557
)
495558
def test_valid_functions(name, instance, response):
@@ -529,6 +592,16 @@ def test_valid_functions(name, instance, response):
529592
},
530593
}
531594
),
595+
resources={
596+
"MyResource": Resource(
597+
{
598+
"Type": "AWS::S3::Bucket",
599+
"Properties": {
600+
"BucketName": "XXX",
601+
},
602+
}
603+
),
604+
},
532605
)
533606
_resolve(name, instance, response, context=context)
534607

0 commit comments

Comments
 (0)