Skip to content

Commit c1f8b15

Browse files
authored
Add equal vars for Rules to cnf building (#3714)
1 parent 9782e80 commit c1f8b15

File tree

3 files changed

+114
-21
lines changed

3 files changed

+114
-21
lines changed

src/cfnlint/conditions/_equals.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from typing import Any, Mapping, Tuple
1111

1212
from sympy import Symbol
13-
from sympy.logic.boolalg import BooleanFalse, BooleanFunction, BooleanTrue
13+
from sympy.logic.boolalg import BooleanFalse, BooleanTrue
1414

1515
from cfnlint.conditions._utils import get_hash
1616
from cfnlint.helpers import is_function
@@ -145,7 +145,9 @@ def left(self):
145145
def right(self):
146146
return self._right
147147

148-
def build_cnf(self, params: dict[str, Symbol]) -> BooleanFunction:
148+
def build_cnf(
149+
self, params: dict[str, Symbol]
150+
) -> BooleanTrue | BooleanFalse | Symbol:
149151
"""Build a SymPy CNF solver based on the provided params
150152
Args:
151153
params dict[str, Symbol]: params is a dict that represents
@@ -158,10 +160,7 @@ def build_cnf(self, params: dict[str, Symbol]) -> BooleanFunction:
158160
return BooleanTrue()
159161
return BooleanFalse()
160162

161-
if self.hash in params:
162-
return params.get(self.hash)
163-
164-
return Symbol(self.hash)
163+
return params.get(self.hash, Symbol(self.hash))
165164

166165
def test(self, scenarios: Mapping[str, str]) -> bool:
167166
"""Do an equals based on the provided scenario"""

src/cfnlint/conditions/conditions.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -110,13 +110,12 @@ def _build_cnf(
110110
cnf = EncodedCNF()
111111

112112
# build parameters and equals into solver
113-
equal_vars: dict[str, Symbol] = {}
113+
equal_vars: dict[str, Symbol | BooleanFalse | BooleanTrue] = {}
114114

115115
equals: dict[str, Equal] = {}
116-
for condition_name in condition_names:
117-
c_equals = self._conditions[condition_name].equals
116+
117+
def _build_equal_vars(c_equals: list[Equal]):
118118
for c_equal in c_equals:
119-
# check to see if equals already matches another one
120119
if c_equal.hash in equal_vars:
121120
continue
122121

@@ -139,6 +138,12 @@ def _build_cnf(
139138
)
140139
equals[c_equal.hash] = c_equal
141140

141+
for rule in self._rules:
142+
_build_equal_vars(rule.equals)
143+
144+
for condition_name in condition_names:
145+
_build_equal_vars(self._conditions[condition_name].equals)
146+
142147
# Determine if a set of conditions can never be all false
143148
allowed_values = self._parameters.copy()
144149
if allowed_values:

test/unit/module/conditions/test_rules.py

Lines changed: 100 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,14 @@ def test_conditions_with_rules(self):
2323
Assertions:
2424
- Assert:
2525
Fn::And:
26-
- !Condition IsProd
27-
- !Condition IsUsEast1
26+
- !Equals [!Ref Environment, "prod"]
27+
- !Equals [!Ref "AWS::Region", "us-east-1"]
2828
Rule2:
2929
Assertions:
3030
- Assert:
3131
Fn::Or:
32-
- !Condition IsProd
33-
- !Condition IsUsEast1
32+
- !Equals [!Ref Environment, "prod"]
33+
- !Equals [!Ref "AWS::Region", "us-east-1"]
3434
"""
3535
)[0]
3636

@@ -79,9 +79,9 @@ def test_conditions_with_rules_implies(self):
7979
IsUsEast1: !Equals [!Ref "AWS::Region", "us-east-1"]
8080
Rules:
8181
Rule:
82-
RuleCondition: !Condition IsProd
82+
RuleCondition: !Equals [!Ref Environment, "prod"]
8383
Assertions:
84-
- Assert: !Condition IsUsEast1
84+
- Assert: !Equals [!Ref "AWS::Region", "us-east-1"]
8585
8686
"""
8787
)[0]
@@ -143,11 +143,11 @@ def test_conditions_with_multiple_rules(self):
143143
Rule1:
144144
RuleCondition: !Equals [!Ref Environment, "prod"]
145145
Assertions:
146-
- Assert: !Condition IsUsEast1
146+
- Assert: !Equals [!Ref "AWS::Region", "us-east-1"]
147147
Rule2:
148148
RuleCondition: !Equals [!Ref Environment, "dev"]
149149
Assertions:
150-
- Assert: !Not [!Condition IsUsEast1]
150+
- Assert: !Not [!Equals [!Ref "AWS::Region", "us-east-1"]]
151151
"""
152152
)[0]
153153

@@ -366,6 +366,95 @@ def test_fn_equals_assertions_ref_never_satisfiable(self):
366366
)
367367
)
368368

369+
def test_conditions_with_rules_and_parameters(self):
370+
template = decode_str(
371+
"""
372+
Conditions:
373+
DeployGateway: !Equals
374+
- !Ref 'DeployGateway'
375+
- 'true'
376+
DeployVpc: !Equals
377+
- !Ref 'DeployVpc'
378+
- 'true'
379+
Parameters:
380+
DeployAnything:
381+
AllowedValues:
382+
- 'false'
383+
- 'true'
384+
Type: 'String'
385+
DeployGateway:
386+
AllowedValues:
387+
- 'false'
388+
- 'true'
389+
Type: 'String'
390+
DeployVpc:
391+
AllowedValues:
392+
- 'false'
393+
- 'true'
394+
Type: 'String'
395+
Rules:
396+
DeployGateway:
397+
Assertions:
398+
- Assert: !Or
399+
- !Equals
400+
- !Ref 'DeployAnything'
401+
- 'true'
402+
- !Equals
403+
- !Ref 'DeployGateway'
404+
- 'false'
405+
DeployVpc:
406+
Assertions:
407+
- Assert: !Or
408+
- !Equals
409+
- !Ref 'DeployGateway'
410+
- 'true'
411+
- !Equals
412+
- !Ref 'DeployVpc'
413+
- 'false'
414+
Resources:
415+
InternetGateway:
416+
Condition: 'DeployGateway'
417+
Type: 'AWS::EC2::InternetGateway'
418+
InternetGatewayAttachment:
419+
Condition: 'DeployVpc'
420+
Type: 'AWS::EC2::VPCGatewayAttachment'
421+
Properties:
422+
InternetGatewayId: !Ref 'InternetGateway'
423+
VpcId: !Ref 'Vpc'
424+
"""
425+
)[0]
426+
427+
cfn = Template("", template)
428+
self.assertEqual(len(cfn.conditions._conditions), 2)
429+
self.assertEqual(len(cfn.conditions._rules), 2)
430+
431+
self.assertListEqual(
432+
[equal.hash for equal in cfn.conditions._rules[0].equals],
433+
[
434+
"d0d70a1e66dc83d7a0fce24c2eca396af1f34e53",
435+
"bbf5c94c1a4b5a79c7a7863fe9463884cb422450",
436+
],
437+
)
438+
439+
self.assertTrue(
440+
cfn.conditions.satisfiable(
441+
{},
442+
{},
443+
)
444+
)
445+
446+
self.assertTrue(
447+
cfn.conditions.check_implies({"DeployVpc": True}, "DeployGateway")
448+
)
449+
450+
self.assertFalse(
451+
cfn.conditions.check_implies({"DeployVpc": False}, "DeployGateway")
452+
)
453+
454+
self.assertFalse(
455+
cfn.conditions.check_implies({"DeployGateway": False}, "DeployVpc")
456+
)
457+
369458

370459
class TestAssertion(TestCase):
371460
def test_assertion_errors(self):
@@ -405,7 +494,7 @@ def test_init_rules_with_wrong_assertions_type(self):
405494
Assertions: {"Foo": "Bar"}
406495
Rule2:
407496
Assertions:
408-
- Assert: !Condition IsUsEast1
497+
- Assert: !Equals [!Ref "AWS::Region", "us-east-1"]
409498
"""
410499
)[0]
411500

@@ -425,8 +514,8 @@ def test_init_rules_with_no_keys(self):
425514
Assertions:
426515
- Assert:
427516
Fn::Or:
428-
- !Condition IsNotUsEast1
429-
- !Condition IsUsEast1
517+
- !Not [!Equals [!Ref "AWS::Region", "us-east-1"]]
518+
- !Equals [!Ref "AWS::Region", "us-east-1"]
430519
Rule3: []
431520
"""
432521
)[0]

0 commit comments

Comments
 (0)