Skip to content

Commit

Permalink
Switch FN support inside Rules (#3712)
Browse files Browse the repository at this point in the history
  • Loading branch information
kddejong authored Sep 24, 2024
1 parent c944440 commit 9782e80
Show file tree
Hide file tree
Showing 6 changed files with 343 additions and 21 deletions.
20 changes: 13 additions & 7 deletions src/cfnlint/rules/conditions/And.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from typing import Any

from cfnlint.helpers import FUNCTION_RULES
from cfnlint.jsonschema import Validator
from cfnlint.rules.functions._BaseFn import BaseFn

Expand All @@ -25,18 +26,23 @@ def __init__(self) -> None:
self.fn_and = self.validate

def schema(self, validator: Validator, instance: Any) -> dict[str, Any]:
if validator.context.path.path and validator.context.path.path[0] == "Rules":
functions = list(FUNCTION_RULES)
else:
functions = [
"Condition",
"Fn::Equals",
"Fn::Not",
"Fn::And",
"Fn::Or",
]

return {
"type": "array",
"minItems": 2,
"maxItems": 10,
"fn_items": {
"functions": [
"Condition",
"Fn::Equals",
"Fn::Not",
"Fn::And",
"Fn::Or",
],
"functions": functions,
"schema": {
"type": ["boolean"],
},
Expand Down
21 changes: 14 additions & 7 deletions src/cfnlint/rules/conditions/Not.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from typing import Any

from cfnlint.helpers import FUNCTION_RULES
from cfnlint.jsonschema import Validator
from cfnlint.rules.functions._BaseFn import BaseFn

Expand All @@ -25,18 +26,24 @@ def __init__(self) -> None:
self.fn_not = self.validate

def schema(self, validator: Validator, instance: Any) -> dict[str, Any]:

if validator.context.path.path and validator.context.path.path[0] == "Rules":
functions = list(FUNCTION_RULES)
else:
functions = [
"Condition",
"Fn::Equals",
"Fn::Not",
"Fn::And",
"Fn::Or",
]

return {
"type": "array",
"maxItems": 1,
"minItems": 1,
"fn_items": {
"functions": [
"Condition",
"Fn::Equals",
"Fn::Not",
"Fn::And",
"Fn::Or",
],
"functions": functions,
"schema": {
"type": ["boolean"],
},
Expand Down
20 changes: 13 additions & 7 deletions src/cfnlint/rules/conditions/Or.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from typing import Any

from cfnlint.helpers import FUNCTION_RULES
from cfnlint.jsonschema import Validator
from cfnlint.rules.functions._BaseFn import BaseFn

Expand All @@ -25,18 +26,23 @@ def __init__(self) -> None:
self.fn_or = self.validate

def schema(self, validator: Validator, instance: Any) -> dict[str, Any]:
if validator.context.path.path and validator.context.path.path[0] == "Rules":
functions = list(FUNCTION_RULES)
else:
functions = [
"Condition",
"Fn::Equals",
"Fn::Not",
"Fn::And",
"Fn::Or",
]

return {
"type": "array",
"minItems": 2,
"maxItems": 10,
"fn_items": {
"functions": [
"Condition",
"Fn::Equals",
"Fn::Not",
"Fn::And",
"Fn::Or",
],
"functions": functions,
"schema": {
"type": ["boolean"],
},
Expand Down
101 changes: 101 additions & 0 deletions test/unit/rules/conditions/test_and.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
"""
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
SPDX-License-Identifier: MIT-0
"""

from collections import deque

import pytest

from cfnlint.jsonschema import ValidationError
from cfnlint.rules.conditions.And import And


@pytest.fixture
def rule():
rule = And()
yield rule


@pytest.fixture
def template():
return {
"Parameters": {
"Environment": {
"Type": "String",
}
},
"Conditions": {
"IsUsEast1": {"Fn::Equals": [{"Ref": "AWS::Region"}, "us-east-1"]},
"IsProduction": {"Fn::Equals": [{"Ref": "Environment"}, "Production"]},
},
}


@pytest.mark.parametrize(
"name,instance,path,errors",
[
(
"Valid and with other conditions",
{"Fn::And": [{"Condition": "IsUsEast1"}, {"Condition": "IsProduction"}]},
{},
[],
),
(
"Valid and with boolean types",
{"Fn::And": [True, False]},
{},
[],
),
(
"Invalid Type",
{"Fn::And": {}},
{},
[
ValidationError(
"{} is not of type 'array'",
validator="fn_and",
schema_path=deque(["type"]),
path=deque(["Fn::And"]),
),
],
),
(
"Integer type",
{"Fn::And": ["a", True]},
{},
[
ValidationError(
"'a' is not of type 'boolean'",
validator="fn_and",
schema_path=deque(["fn_items", "type"]),
path=deque(["Fn::And", 0]),
)
],
),
(
"Invalid functions in Conditions",
{"Fn::And": [True, {"Fn::Contains": []}]},
{"path": deque(["Conditions", "Condition1"])},
[
ValidationError(
"{'Fn::Contains': []} is not of type 'boolean'",
validator="fn_and",
schema_path=deque(["fn_items", "type"]),
path=deque(["Fn::And", 1]),
)
],
),
(
"Valid functions in Rules",
{"Fn::And": [True, {"Fn::Contains": []}]},
{"path": deque(["Rules", "Rule1"])},
[],
),
],
indirect=["path"],
)
def test_condition(name, instance, errors, rule, validator):
errs = list(rule.validate(validator, {}, instance, {}))

assert errs == errors, name
101 changes: 101 additions & 0 deletions test/unit/rules/conditions/test_not.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
"""
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
SPDX-License-Identifier: MIT-0
"""

from collections import deque

import pytest

from cfnlint.jsonschema import ValidationError
from cfnlint.rules.conditions.Not import Not


@pytest.fixture
def rule():
rule = Not()
yield rule


@pytest.fixture
def template():
return {
"Parameters": {
"Environment": {
"Type": "String",
}
},
"Conditions": {
"IsUsEast1": {"Fn::Equals": [{"Ref": "AWS::Region"}, "us-east-1"]},
"IsProduction": {"Fn::Equals": [{"Ref": "Environment"}, "Production"]},
},
}


@pytest.mark.parametrize(
"name,instance,path,errors",
[
(
"Valid or with other conditions",
{"Fn::Not": [{"Condition": "IsUsEast1"}]},
{},
[],
),
(
"Valid or with boolean types",
{"Fn::Not": [True]},
{},
[],
),
(
"Invalid Type",
{"Fn::Not": {}},
{},
[
ValidationError(
"{} is not of type 'array'",
validator="fn_not",
schema_path=deque(["type"]),
path=deque(["Fn::Not"]),
),
],
),
(
"Integer type",
{"Fn::Not": ["a"]},
{},
[
ValidationError(
"'a' is not of type 'boolean'",
validator="fn_not",
schema_path=deque(["fn_items", "type"]),
path=deque(["Fn::Not", 0]),
)
],
),
(
"Invalid functions in Conditions",
{"Fn::Not": [{"Fn::Contains": []}]},
{"path": deque(["Conditions", "Condition1"])},
[
ValidationError(
"{'Fn::Contains': []} is not of type 'boolean'",
validator="fn_not",
schema_path=deque(["fn_items", "type"]),
path=deque(["Fn::Not", 0]),
)
],
),
(
"Valid functions in Rules",
{"Fn::Not": [{"Fn::Contains": []}]},
{"path": deque(["Rules", "Rule1"])},
[],
),
],
indirect=["path"],
)
def test_condition(name, instance, errors, rule, validator):
errs = list(rule.validate(validator, {}, instance, {}))

assert errs == errors, name
Loading

0 comments on commit 9782e80

Please sign in to comment.