Skip to content

Commit 5b2d325

Browse files
authored
Check condition availability on region based conditions (#2668)
1 parent 66c586d commit 5b2d325

File tree

3 files changed

+71
-9
lines changed

3 files changed

+71
-9
lines changed

src/cfnlint/conditions/conditions.py

+39-7
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import itertools
66
import logging
77
import traceback
8-
from typing import Any, Dict, Iterator, List, Tuple
8+
from typing import Any, Dict, Generator, Iterator, List, Tuple
99

1010
from sympy import And, Implies, Not, Symbol
1111
from sympy.assumptions.cnf import EncodedCNF
@@ -162,12 +162,6 @@ def build_scenarios(self, condition_names: List[str]) -> Iterator[Dict[str, bool
162162
if len(condition_names) == 0:
163163
return
164164

165-
# if only one condition we will assume its True/False
166-
# if len(condition_names) == 1:
167-
# yield {condition_names[0]: True}
168-
# yield {condition_names[0]: False}
169-
# return
170-
171165
try:
172166
# build a large matric of True/False options based on the provided conditions
173167
scenarios_returned = 0
@@ -255,3 +249,41 @@ def check_implies(self, scenarios: Dict[str, bool], implies: str) -> bool:
255249
# KeyError is because the listed condition doesn't exist because of bad
256250
# formatting or just the wrong condition name
257251
return True
252+
253+
def build_scenerios_on_region(
254+
self, condition_name: str, region: str
255+
) -> Generator[bool, None, None]:
256+
"""Based on a region validate if the condition_name coudle be true
257+
258+
Args:
259+
condition_name (str): The name of the condition we are validating against
260+
region (str): the name of the region
261+
262+
Returns:
263+
Generator[bool]: Returns True, False, or True and False depending on if the
264+
condition could be True, False or both based on the region parameter
265+
"""
266+
if not isinstance(condition_name, str):
267+
return
268+
cnf_region = self._cnf.copy()
269+
for eql in self._conditions[condition_name].equals:
270+
is_region, equal_region = eql.is_region
271+
if is_region:
272+
if equal_region == region:
273+
cnf_region.add_prop(And(self._solver_params[eql.hash]))
274+
else:
275+
cnf_region.add_prop(Not(self._solver_params[eql.hash]))
276+
277+
cnf_test = cnf_region.copy()
278+
cnf_test.add_prop(
279+
self._conditions[condition_name].build_true_cnf(self._solver_params)
280+
)
281+
if satisfiable(cnf_test):
282+
yield True
283+
284+
cnf_test = cnf_region.copy()
285+
cnf_test.add_prop(
286+
self._conditions[condition_name].build_false_cnf(self._solver_params)
287+
)
288+
if satisfiable(cnf_test):
289+
yield False

src/cfnlint/conditions/equals.py

+25-1
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@
44
"""
55
import json
66
import logging
7-
from typing import Any, Dict, List, Union
7+
from typing import Any, Dict, List, Tuple, Union
88

99
from cfnlint.conditions._utils import get_hash
1010

1111
LOGGER = logging.getLogger(__name__)
12+
REF_REGION = get_hash({"Ref": "AWS::Region"})
1213

1314

1415
class EqualParameter:
@@ -27,6 +28,7 @@ class Equal:
2728
_left: Union[EqualParameter, str]
2829
_right: Union[EqualParameter, str]
2930
_is_static: Union[bool, None]
31+
_is_region: Tuple[bool, str]
3032

3133
def __init__(self, equal: List[Union[str, dict]]) -> None:
3234
self._is_static = None
@@ -45,6 +47,15 @@ def __init__(self, equal: List[Union[str, dict]]) -> None:
4547
self._right, EqualParameter
4648
):
4749
self._is_static = self._left == self._right
50+
51+
self._is_region = (False, "")
52+
if isinstance(self._left, EqualParameter):
53+
if self._left.hash == REF_REGION and isinstance(self._right, str):
54+
self._is_region = (True, self._right)
55+
if isinstance(self._right, EqualParameter):
56+
if self._right.hash == REF_REGION and isinstance(self._left, str):
57+
self._is_region = (True, self._left)
58+
4859
return
4960
raise ValueError("Equals has to be a list of two values")
5061

@@ -85,6 +96,19 @@ def parameters(self) -> List[EqualParameter]:
8596
params.append(self._right)
8697
return params
8798

99+
@property
100+
def is_region(self) -> Tuple[bool, str]:
101+
"""Returns a Tuple if the condition is comparing a region to a string
102+
103+
Args: None
104+
105+
Returns:
106+
Tuple[bool, str]: Tuple where the boolean is True if the condition
107+
is using Ref: AWS::Region and the second element is for the region
108+
being compared
109+
"""
110+
return self._is_region
111+
88112
@property
89113
def left(self):
90114
return self._left

src/cfnlint/rules/resources/Configuration.py

+7-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import cfnlint.helpers
66
from cfnlint.helpers import REGISTRY_SCHEMAS
77
from cfnlint.rules import CloudFormationLintRule, RuleMatch
8+
from cfnlint.template import Template
89

910

1011
class Configuration(CloudFormationLintRule):
@@ -18,7 +19,7 @@ class Configuration(CloudFormationLintRule):
1819
source_url = "https://github.com/aws-cloudformation/cfn-python-lint"
1920
tags = ["resources"]
2021

21-
def _check_resource(self, cfn, resource_name, resource_values):
22+
def _check_resource(self, cfn: Template, resource_name, resource_values):
2223
"""Check Resource"""
2324

2425
valid_attributes = [
@@ -110,6 +111,11 @@ def _check_resource(self, cfn, resource_name, resource_values):
110111
self.logger.debug("Check resource types by region...")
111112
for region, specs in cfnlint.helpers.RESOURCE_SPECS.items():
112113
if region in cfn.regions:
114+
if condition:
115+
if False in cfn.conditions.build_scenerios_on_region(
116+
condition, region
117+
):
118+
continue
113119
if resource_type not in specs[
114120
"ResourceTypes"
115121
] and resource_type not in [

0 commit comments

Comments
 (0)