Skip to content

Commit 572bd51

Browse files
kftsehkkddejong
andauthored
customer operator: regex, gt, lt (#2694)
Co-authored-by: Kevin DeJong <[email protected]>
1 parent cb587bf commit 572bd51

File tree

8 files changed

+129
-5
lines changed

8 files changed

+129
-5
lines changed

docs/custom_rules.md

+3
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,12 @@ The specified operator to be used for this rule. The supported values are define
4141
| == | Identical to `EQUALS` |
4242
| NOT_EQUALS | Checks the specified property is not equal to the value given |
4343
| != | Identical to `NOT_EQUALS` |
44+
| REGEX_MATCH | Checks the specified property matches regex given (using python `regex` module) |
4445
| IN | Checks the specified property is equal to or contained by the array value |
4546
| NOT_IN | Checks the specified property is not equal to or not contained by the array value |
4647
| \>= | Checks the specified property is greater than or equal to the value given |
48+
| \> | Checks the specified property is greater than the value given |
49+
| < | Checks the specified property is less than the value given |
4750
| <= | Checks the specified property is less than or equal to the value given |
4851
| IS | Checks the specified property is defined or not defined, the value must be one of DEFINED or NOT_DEFINED |
4952

src/cfnlint/rules/custom/Operators.py

+90-1
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,21 @@
33
SPDX-License-Identifier: MIT-0
44
"""
55

6+
import regex as re
7+
68
# pylint: disable=cyclic-import
79
import cfnlint.rules
810

911
OPERATOR = [
1012
"EQUALS",
1113
"NOT_EQUALS",
14+
"REGEX_MATCH",
1215
"==",
1316
"!=",
1417
"IN",
1518
"NOT_IN",
19+
">",
20+
"<",
1621
">=",
1722
"<=",
1823
"IS DEFINED",
@@ -298,7 +303,29 @@ def rule_func(value, expected_values, path):
298303
)
299304

300305

301-
def CreateGreaterRule(rule_id, resourceType, prop, value, error_message):
306+
def CreateRegexMatchRule(rule_id, resourceType, prop, value, error_message):
307+
def rule_func(value, expected_values, path):
308+
matches = []
309+
if not re.match(expected_values.strip(), str(value).strip()):
310+
matches.append(
311+
cfnlint.rules.RuleMatch(path, error_message or "Regex does not match")
312+
)
313+
314+
return matches
315+
316+
return CreateCustomRule(
317+
rule_id,
318+
resourceType,
319+
prop,
320+
value,
321+
error_message,
322+
shortdesc="Custom rule to check for regex match",
323+
description="Created from the custom rules parameter. This rule will check if a property value match the provided regex pattern.",
324+
rule_func=rule_func,
325+
)
326+
327+
328+
def CreateGreaterEqualRule(rule_id, resourceType, prop, value, error_message):
302329
def rule_func(value, expected_value, path):
303330
matches = []
304331
if checkInt(str(value).strip()) and checkInt(str(expected_value).strip()):
@@ -329,7 +356,69 @@ def rule_func(value, expected_value, path):
329356
)
330357

331358

359+
def CreateGreaterRule(rule_id, resourceType, prop, value, error_message):
360+
def rule_func(value, expected_value, path):
361+
matches = []
362+
if checkInt(str(value).strip()) and checkInt(str(expected_value).strip()):
363+
if int(str(value).strip()) <= int(str(expected_value).strip()):
364+
matches.append(
365+
cfnlint.rules.RuleMatch(
366+
path, error_message or "Greater than check failed"
367+
)
368+
)
369+
else:
370+
matches.append(
371+
cfnlint.rules.RuleMatch(
372+
path, error_message or "Given values are not numeric"
373+
)
374+
)
375+
376+
return matches
377+
378+
return CreateCustomRule(
379+
rule_id,
380+
resourceType,
381+
prop,
382+
value,
383+
error_message,
384+
shortdesc="Custom rule to check for if a value is greater than the specified value",
385+
description="Created from the custom rules parameter. This rule will check if a property value is greater than the specified value.",
386+
rule_func=rule_func,
387+
)
388+
389+
332390
def CreateLesserRule(rule_id, resourceType, prop, value, error_message):
391+
def rule_func(value, expected_value, path):
392+
matches = []
393+
if checkInt(str(value).strip()) and checkInt(str(expected_value).strip()):
394+
if int(str(value).strip()) >= int(str(expected_value).strip()):
395+
matches.append(
396+
cfnlint.rules.RuleMatch(
397+
path, error_message or "Lesser than check failed"
398+
)
399+
)
400+
else:
401+
matches.append(
402+
cfnlint.rules.RuleMatch(
403+
path, error_message or "Given values are not numeric"
404+
)
405+
)
406+
407+
return matches
408+
409+
return CreateCustomRule(
410+
rule_id,
411+
resourceType,
412+
prop,
413+
value,
414+
error_message,
415+
shortdesc="Custom rule to check for if a value is lesser than the specified value",
416+
description="Created from the custom rules parameter. This rule will check if a property value is lesser than the specified value.",
417+
rule_func=rule_func,
418+
)
419+
420+
421+
def CreateLesserEqualRule(rule_id, resourceType, prop, value, error_message):
333422
def rule_func(value, expected_value, path):
334423
matches = []
335424
if checkInt(str(value).strip()) and checkInt(str(expected_value).strip()):

src/cfnlint/rules/custom/__init__.py

+14-2
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ def process_sets(raw_value):
7272
return cfnlint.rules.custom.Operators.CreateNotEqualsRule(
7373
error_level + str(rule_id), resourceType, prop, value, error_message
7474
)
75+
if operator == "REGEX_MATCH":
76+
return cfnlint.rules.custom.Operators.CreateRegexMatchRule(
77+
error_level + str(rule_id), resourceType, prop, value, error_message
78+
)
7579
if operator == "IN":
7680
return cfnlint.rules.custom.Operators.CreateInSetRule(
7781
error_level + str(rule_id), resourceType, prop, value, error_message
@@ -80,14 +84,22 @@ def process_sets(raw_value):
8084
return cfnlint.rules.custom.Operators.CreateNotInSetRule(
8185
error_level + str(rule_id), resourceType, prop, value, error_message
8286
)
83-
if operator == ">=":
87+
if operator == ">":
8488
return cfnlint.rules.custom.Operators.CreateGreaterRule(
8589
error_level + str(rule_id), resourceType, prop, value, error_message
8690
)
87-
if operator == "<=":
91+
if operator == ">=":
92+
return cfnlint.rules.custom.Operators.CreateGreaterEqualRule(
93+
error_level + str(rule_id), resourceType, prop, value, error_message
94+
)
95+
if operator == "<":
8896
return cfnlint.rules.custom.Operators.CreateLesserRule(
8997
error_level + str(rule_id), resourceType, prop, value, error_message
9098
)
99+
if operator == "<=":
100+
return cfnlint.rules.custom.Operators.CreateLesserEqualRule(
101+
error_level + str(rule_id), resourceType, prop, value, error_message
102+
)
91103
if operator == "IS":
92104
if value in ["DEFINED", "NOT_DEFINED"]:
93105
return cfnlint.rules.custom.Operators.CreateCustomIsDefinedRule(
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
AWS::ElasticLoadBalancing::LoadBalancer HealthCheck.HealthyThreshold >= 5
2-
AWS::ElasticLoadBalancing::LoadBalancer HealthCheck.HealthyThreshold >= AB
2+
AWS::ElasticLoadBalancing::LoadBalancer HealthCheck.HealthyThreshold > 3
3+
AWS::ElasticLoadBalancing::LoadBalancer HealthCheck.HealthyThreshold >= AB
4+
AWS::ElasticLoadBalancing::LoadBalancer HealthCheck.HealthyThreshold > AB
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
AWS::ElasticLoadBalancing::LoadBalancer HealthCheck.HealthyThreshold <= 1
2-
AWS::ElasticLoadBalancing::LoadBalancer HealthCheck.HealthyThreshold <= AB
2+
AWS::ElasticLoadBalancing::LoadBalancer HealthCheck.HealthyThreshold < 3
3+
AWS::ElasticLoadBalancing::LoadBalancer HealthCheck.HealthyThreshold <= AB
4+
AWS::ElasticLoadBalancing::LoadBalancer HealthCheck.HealthyThreshold < AB
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
AWS::IAM::Role AssumeRolePolicyDocument.Version REGEX_MATCH "^202.*$"

test/fixtures/custom_rules/good/custom_rule_perfect.txt

+5
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,10 @@ AWS::IAM::Policy PolicyName EQUALS "root" WARN ABC
66
AWS::IAM::Policy PolicyName IN [2012-10-16,root,2012-10-18] ERROR ABC
77
AWS::IAM::Policy PolicyName NOT_EQUALS "user" WARN ABC
88
AWS::IAM::Policy PolicyName NOT_IN [2012-10-16,2012-11-20,2012-10-18] ERROR ABC
9+
AWS::EC2::Instance BlockDeviceMappings.Ebs.VolumeSize >= 20 WARN
10+
AWS::EC2::Instance BlockDeviceMappings.Ebs.VolumeSize > 10 ERROR ABC
11+
AWS::EC2::Instance BlockDeviceMappings.Ebs.VolumeSize <= 50 ERROR DEF
12+
AWS::EC2::Instance BlockDeviceMappings.Ebs.VolumeSize < 40 WARN ABC
13+
AWS::CloudFormation::Stack TemplateURL REGEX_MATCH "^https.*$" WARN ABC
914
AWS::Lambda::Function Environment.Variables.NODE_ENV IS DEFINED
1015
AWS::Lambda::Function Environment.Variables.PRIVATE_KEY IS NOT_DEFINED

test/unit/module/custom_rules/test_custom_rules.py

+10
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ def setUp(self):
5656
self.invalid_less_than = (
5757
"test/fixtures/custom_rules/bad/custom_rule_invalid_less_than.txt"
5858
)
59+
self.invalid_regex = (
60+
"test/fixtures/custom_rules/bad/custom_rule_invalid_regex.txt"
61+
)
5962

6063
def test_perfect_parse(self):
6164
"""Test Successful Custom_Rule Parsing"""
@@ -102,6 +105,13 @@ def test_invalid_less_than(self):
102105
> -1
103106
)
104107

108+
def test_invalid_regex(self):
109+
"""Test Successful Custom_Rule Parsing"""
110+
assert (
111+
self.run_tests(self.invalid_regex)[0].message.find("Regex does not match")
112+
> -1
113+
)
114+
105115
def test_valid_boolean_value(self):
106116
"""Test Boolean values"""
107117
assert self.run_tests(self.valid_boolean) == []

0 commit comments

Comments
 (0)