forked from aws-cloudformation/cfn-lint
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathListDuplicatesAllowed.py
104 lines (85 loc) · 4.24 KB
/
ListDuplicatesAllowed.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
"""
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
SPDX-License-Identifier: MIT-0
"""
import hashlib
import json
from cfnlint.rules import CloudFormationLintRule
from cfnlint.rules import RuleMatch
from cfnlint.helpers import RESOURCE_SPECS
class ListDuplicatesAllowed(CloudFormationLintRule):
"""Check if duplicates exist in a List"""
id = 'I3037'
shortdesc = 'Check if a list that allows duplicates has any duplicates'
description = 'Certain lists support duplicate items.' \
'Provide an alert when list of strings or numbers have repeats.'
source_url = 'https://github.com/aws-cloudformation/cfn-python-lint/blob/main/docs/rules.md#rules-1'
tags = ['resources', 'property', 'list']
def initialize(self, cfn):
"""Initialize the rule"""
for resource_type_spec in RESOURCE_SPECS.get(cfn.regions[0]).get('ResourceTypes'):
self.resource_property_types.append(resource_type_spec)
for property_type_spec in RESOURCE_SPECS.get(cfn.regions[0]).get('PropertyTypes'):
self.resource_sub_property_types.append(property_type_spec)
def _check_duplicates(self, values, path, scenario=None):
""" Check for Duplicates """
matches = []
list_items = []
if isinstance(values, list):
for index, value in enumerate(values):
value_hash = hashlib.sha1(json.dumps(
value, sort_keys=True).encode('utf-8')).hexdigest()
if value_hash in list_items:
if not scenario:
message = 'List has a duplicate value at {0}'
matches.append(
RuleMatch(path + [index], message.format('/'.join(map(str, path + [index])))))
else:
scenario_text = ' and '.join(
['condition "%s" is %s' % (k, v) for (k, v) in scenario.items()])
message = 'List has a duplicate value at {0} when {1}'
matches.append(RuleMatch(path, message.format(
'/'.join(map(str, path)), scenario_text)))
list_items.append(value_hash)
return matches
def check_duplicates(self, values, path, cfn):
""" Check for duplicates """
matches = []
if isinstance(values, list):
matches.extend(self._check_duplicates(values, path))
elif isinstance(values, dict):
props = cfn.get_object_without_conditions(values)
for prop in props:
matches.extend(self._check_duplicates(
prop.get('Object'), path, prop.get('Scenario')))
return matches
def check(self, cfn, properties, value_specs, path):
"""Check itself"""
matches = list()
for p_value, p_path in properties.items_safe(path[:]):
for prop in p_value:
if prop in value_specs:
property_type = value_specs.get(prop).get('Type')
primitive_type = value_specs.get(prop).get('PrimitiveItemType')
duplicates_allowed = value_specs.get(prop).get('DuplicatesAllowed', False)
if property_type == 'List' and duplicates_allowed and primitive_type in ['String', 'Integer']:
matches.extend(
self.check_duplicates(
p_value[prop], p_path + [prop], cfn
)
)
return matches
def match_resource_sub_properties(self, properties, property_type, path, cfn):
"""Match for sub properties"""
matches = list()
specs = RESOURCE_SPECS.get(cfn.regions[0]).get(
'PropertyTypes').get(property_type, {}).get('Properties', {})
matches.extend(self.check(cfn, properties, specs, path))
return matches
def match_resource_properties(self, properties, resource_type, path, cfn):
"""Check CloudFormation Properties"""
matches = list()
specs = RESOURCE_SPECS.get(cfn.regions[0]).get(
'ResourceTypes').get(resource_type, {}).get('Properties', {})
matches.extend(self.check(cfn, properties, specs, path))
return matches