Skip to content

Commit 31ac633

Browse files
authored
Consider output conditions in E6101 (#3414)
1 parent 36d79c6 commit 31ac633

File tree

3 files changed

+183
-13
lines changed

3 files changed

+183
-13
lines changed

src/cfnlint/rules/outputs/Value.py

+15-2
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,25 @@ class Value(CfnLintJsonSchema):
2323

2424
def __init__(self):
2525
super().__init__(
26-
keywords=["Outputs/*/Value"],
26+
keywords=["Outputs/*"],
2727
all_matches=True,
2828
)
2929

3030
def validate(self, validator: Validator, _: Any, instance: Any, schema: Any):
31+
value = instance.get("Value")
32+
if not value:
33+
return
34+
35+
conditions = {}
36+
condition = instance.get("Condition")
37+
if condition:
38+
conditions = {condition: True}
3139
validator = validator.evolve(
3240
context=validator.context.evolve(
3341
functions=list(FUNCTIONS),
42+
conditions=validator.context.conditions.evolve(
43+
conditions,
44+
),
3445
),
3546
schema={
3647
"type": ["array", "string"],
@@ -40,4 +51,6 @@ def validate(self, validator: Validator, _: Any, instance: Any, schema: Any):
4051
},
4152
)
4253

43-
yield from self._iter_errors(validator, instance)
54+
for err in self._iter_errors(validator, value):
55+
err.path.appendleft("Value")
56+
yield err

test/integration/test_schema_files.py

+1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ class TestSchemaFiles(TestCase):
3434
"Metadata/AWS::CloudFormation::Interface",
3535
"Metadata/cfn-lint",
3636
"Outputs",
37+
"Outputs/*",
3738
"Outputs/*/Condition",
3839
"Outputs/*/Export/Name",
3940
"Outputs/*/Value",

test/unit/rules/outputs/test_value.py

+167-11
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,183 @@
33
SPDX-License-Identifier: MIT-0
44
"""
55

6+
from collections import deque
7+
68
import pytest
79

8-
from cfnlint.jsonschema import CfnTemplateValidator
10+
from cfnlint.jsonschema import CfnTemplateValidator, ValidationError
11+
from cfnlint.rules.functions.Cidr import Cidr
12+
from cfnlint.rules.functions.Join import Join
13+
from cfnlint.rules.functions.Ref import Ref
914
from cfnlint.rules.outputs.Value import Value # pylint: disable=E0401
1015

1116

17+
@pytest.fixture
18+
def template():
19+
return {
20+
"Parameters": {
21+
"additionalVpcCidr": {
22+
"Type": "String",
23+
"Default": "",
24+
},
25+
"badAdditionalVpcCidr": {
26+
"Type": "String",
27+
"Default": "1",
28+
},
29+
},
30+
"Conditions": {
31+
"isAdditionalVpc": {
32+
"Fn::Not": [
33+
{
34+
"Fn::Equals": [
35+
{"Ref": "additionalVpcCidr"},
36+
"",
37+
]
38+
}
39+
]
40+
},
41+
"isBadAdditionalVpc": {
42+
"Fn::Not": [
43+
{
44+
"Fn::Equals": [
45+
{"Ref": "badAdditionalVpcCidr"},
46+
"",
47+
]
48+
}
49+
]
50+
},
51+
},
52+
}
53+
54+
55+
@pytest.fixture
56+
def validator(cfn):
57+
yield CfnTemplateValidator(schema={}).extend(
58+
validators={
59+
"fn_join": Join().fn_join,
60+
"ref": Ref().ref,
61+
"fn_cidr": Cidr().fn_cidr,
62+
}
63+
)(
64+
schema={},
65+
cfn=cfn,
66+
)
67+
68+
1269
@pytest.mark.parametrize(
1370
"input,expected",
1471
[
15-
("foo", 0),
16-
(1.0, 1),
17-
(1, 1),
18-
(True, 1),
19-
([{}], 1),
20-
({"foo": "bar"}, 1),
72+
(
73+
{
74+
"Value": "foo",
75+
},
76+
[],
77+
),
78+
(
79+
{
80+
"Value": 1.0,
81+
},
82+
[
83+
ValidationError(
84+
"1.0 is not of type 'array', 'string'",
85+
validator="type",
86+
schema_path=deque(["type"]),
87+
path=deque(["Value"]),
88+
rule=Value(),
89+
)
90+
],
91+
),
92+
(
93+
{
94+
"Value": 1,
95+
},
96+
[
97+
ValidationError(
98+
"1 is not of type 'array', 'string'",
99+
validator="type",
100+
schema_path=deque(["type"]),
101+
path=deque(["Value"]),
102+
rule=Value(),
103+
)
104+
],
105+
),
106+
(
107+
{"Value": True},
108+
[
109+
ValidationError(
110+
"True is not of type 'array', 'string'",
111+
validator="type",
112+
schema_path=deque(["type"]),
113+
path=deque(["Value"]),
114+
rule=Value(),
115+
)
116+
],
117+
),
118+
(
119+
{"Value": [{}]},
120+
[
121+
ValidationError(
122+
"{} is not of type 'string'",
123+
validator="type",
124+
schema_path=deque(["items", "type"]),
125+
path=deque(["Value", 0]),
126+
rule=Value(),
127+
)
128+
],
129+
),
130+
(
131+
{"Value": {"foo": "bar"}},
132+
[
133+
ValidationError(
134+
"{'foo': 'bar'} is not of type 'array', 'string'",
135+
validator="type",
136+
schema_path=deque(["type"]),
137+
path=deque(["Value"]),
138+
rule=Value(),
139+
)
140+
],
141+
),
142+
(
143+
{
144+
"Condition": "isAdditionalVpc",
145+
"Value": {
146+
"Fn::Join": [
147+
",",
148+
{"Fn::Cidr": [{"Ref": "additionalVpcCidr"}, 3, 8]},
149+
]
150+
},
151+
},
152+
[],
153+
),
154+
(
155+
{
156+
"Condition": "isBadAdditionalVpc",
157+
"Value": {
158+
"Fn::Join": [
159+
",",
160+
{"Fn::Cidr": [{"Ref": "badAdditionalVpcCidr"}, 3, 8]},
161+
]
162+
},
163+
},
164+
[
165+
ValidationError(
166+
(
167+
"{'Ref': 'badAdditionalVpcCidr'} does not match "
168+
"'^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\\\/([0-9]|[1-2][0-9]|3[0-2]))$'"
169+
" when 'Ref' is resolved"
170+
),
171+
validator="ref",
172+
schema_path=deque(
173+
["fn_join", "fn_items", "fn_cidr", "fn_items", "ref", "pattern"]
174+
),
175+
path=deque(["Value", "Fn::Join", 1, "Fn::Cidr", 0, "Ref"]),
176+
)
177+
],
178+
),
21179
],
22180
)
23-
def test_output_value(input, expected):
181+
def test_output_value(input, expected, validator):
24182
rule = Value()
25-
validator = CfnTemplateValidator()
26-
27183
results = list(rule.validate(validator, {}, input, {}))
28184

29-
assert len(results) == expected, f"Expected {expected} results, got {results}"
185+
assert results == expected, f"Expected {expected} results, got {results}"

0 commit comments

Comments
 (0)