1"""
2Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3SPDX-License-Identifier: MIT-0
4"""
5#pylint: disable=cyclic-import
6import cfnlint.rules
7
8OPERATOR = [
9    'EQUALS',
10    'NOT_EQUALS',
11    '==',
12    '!=',
13    'IN',
14    'NOT_IN',
15    '>=',
16    '<=']
17
18
19def CreateCustomRule(rule_id, resourceType, prop, value, error_message, description, shortdesc, rule_func):
20
21    class CustomRule(cfnlint.rules.CloudFormationLintRule):
22
23        def __init__(self, rule_id, resourceType, prop, value, error_message, description, shortdesc, rule_func):
24            super(CustomRule, self).__init__()
25            self.resource_property_types.append(resourceType)
26            self.id = rule_id
27            self.property_chain = prop.split('.')
28            self.property_value = value
29            self.error_message = error_message
30            self.description = description
31            self.shortdesc = shortdesc
32            self.rule_func = rule_func
33
34        def _remaining_inset_properties(self, property_chain):
35            if len(property_chain) > 1:
36                return property_chain[1:]
37
38            return []
39
40        def _check_value(self, value, path, property_chain, cfn):
41            matches = []
42            if property_chain:
43                new_property_chain = self._remaining_inset_properties(property_chain)
44                matches.extend(
45                    cfn.check_value(
46                        value, property_chain[0], path,
47                        check_value=self._check_value,
48                        property_chain=new_property_chain,
49                        cfn=cfn,
50                    ))
51                return matches
52            if value:
53                matches.extend(self.rule_func(value, self.property_value, path))
54            return matches
55
56        def match_resource_properties(self, properties, _, path, cfn):
57            new_property_chain = self._remaining_inset_properties(self.property_chain)
58            return cfn.check_value(
59                properties, self.property_chain[0], path,
60                check_value=self._check_value,
61                property_chain=new_property_chain,
62                cfn=cfn,
63            )
64
65    return CustomRule(rule_id, resourceType, prop, value, error_message, description, shortdesc, rule_func)
66
67
68def CreateEqualsRule(rule_id, resourceType, prop, value, error_message):
69    def rule_func(value, expected_value, path):
70        matches = []
71        if str(value).strip().lower() != str(expected_value).strip().lower():
72            matches.append(cfnlint.rules.RuleMatch(
73                path, error_message or 'Must equal check failed'))
74
75        return matches
76
77    return CreateCustomRule(
78        rule_id, resourceType, prop, value, error_message,
79        shortdesc='Custom rule to check for equal values',
80        description='Created from the custom rules parameter. This rule will check if a property value is equal to the specified value.',
81        rule_func=rule_func,
82    )
83
84
85def CreateNotEqualsRule(rule_id, resourceType, prop, value, error_message):
86    def rule_func(value, expected_values, path):
87        matches = []
88        if str(value).strip().lower() == str(expected_values).strip().lower():
89            matches.append(cfnlint.rules.RuleMatch(
90                path, error_message or 'Must not equal check failed'))
91
92        return matches
93
94    return CreateCustomRule(
95        rule_id, resourceType, prop, value, error_message,
96        shortdesc='Custom rule to check for not equal values',
97        description='Created from the custom rules parameter. This rule will check if a property value is NOT equal to the specified value.',
98        rule_func=rule_func,
99    )
100
101
102def CreateGreaterRule(rule_id, resourceType, prop, value, error_message):
103    def rule_func(value, expected_value, path):
104        matches = []
105        if checkInt(value.strip()) and checkInt(str(expected_value).strip()):
106            if value.strip().lower() < str(expected_value).strip().lower():
107                matches.append(cfnlint.rules.RuleMatch(
108                    path, error_message or 'Greater than check failed'))
109        else:
110            matches.append(cfnlint.rules.RuleMatch(
111                path, error_message or 'Given values are not numeric'))
112
113        return matches
114
115    return CreateCustomRule(
116        rule_id, resourceType, prop, value, error_message,
117        shortdesc='Custom rule to check for if a value is greater than the specified value',
118        description='Created from the custom rules parameter. This rule will check if a property value is greater than the specified value.',
119        rule_func=rule_func,
120    )
121
122
123def CreateLesserRule(rule_id, resourceType, prop, value, error_message):
124    def rule_func(value, expected_value, path):
125        matches = []
126        if checkInt(value.strip()) and checkInt(str(expected_value).strip()):
127            if value.strip().lower() > str(expected_value).strip().lower():
128                matches.append(cfnlint.rules.RuleMatch(
129                    path, error_message or 'Lesser than check failed'))
130        else:
131            matches.append(cfnlint.rules.RuleMatch(
132                path, error_message or 'Given values are not numeric'))
133
134        return matches
135
136    return CreateCustomRule(
137        rule_id, resourceType, prop, value, error_message,
138        shortdesc='Custom rule to check for if a value is lesser than the specified value',
139        description='Created from the custom rules parameter. This rule will check if a property value is lesser than the specified value.',
140        rule_func=rule_func,
141    )
142
143
144def CreateInSetRule(rule_id, resourceType, prop, value, error_message):
145    def rule_func(value, expected_values, path):
146        matches = []
147        if value not in expected_values:
148            matches.append(cfnlint.rules.RuleMatch(path, error_message or 'In set check failed'))
149
150        return matches
151
152    return CreateCustomRule(
153        rule_id, resourceType, prop, value, error_message,
154        shortdesc='Custom rule to check for if a value exists in a list of specified values',
155        description='Created from the custom rules parameter. This rule will check if a property value exists inside a list of specified values.',
156        rule_func=rule_func,
157    )
158
159
160def CreateNotInSetRule(rule_id, resourceType, prop, value, error_message):
161    def rule_func(value, expected_values, path):
162        matches = []
163        if value in expected_values:
164            matches.append(cfnlint.rules.RuleMatch(
165                path, error_message or 'Not in set check failed'))
166
167        return matches
168
169    return CreateCustomRule(
170        rule_id, resourceType, prop, value, error_message,
171        shortdesc='Custom rule to check for if a value does not exist in a list of specified values',
172        description='Created from the custom rules parameter. This rule will check if a property value does not exist inside a list of specified values.',
173        rule_func=rule_func,
174    )
175
176
177def CreateInvalidRule(rule_id, operator):
178    class InvalidRule(cfnlint.rules.CloudFormationLintRule):
179
180        def __init__(self, rule_id, operator):
181            super(InvalidRule, self).__init__()
182            self.id = rule_id
183            self.operator = operator
184            self.description = 'Created from the custom rule parameter. This rule is the result of an invalid configuration of a custom rule.'
185            self.shortdesc = 'Invalid custom rule configuration'
186
187        def match(self, _):
188            message = '"{0}" not in supported operators: [{1}]'
189            return [
190                cfnlint.rules.RuleMatch(
191                    [], message.format(str(self.operator), ', '.join(OPERATOR)))
192            ]
193
194    return InvalidRule(rule_id, operator)
195
196
197def checkInt(i):
198    """ Python 2.7 Compatibility - There is no isnumeric() method """
199    try:
200        int(i)
201        return True
202    except ValueError:
203        return False
204