forked from aws-cloudformation/cfn-lint
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Configuration.py
124 lines (112 loc) · 5.22 KB
/
Configuration.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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
"""
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
SPDX-License-Identifier: MIT-0
"""
from cfnlint.rules import CloudFormationLintRule
from cfnlint.rules import RuleMatch
from cfnlint.helpers import FUNCTIONS_SINGLE
class Configuration(CloudFormationLintRule):
"""Check if Outputs are configured correctly"""
id = 'E6001'
shortdesc = 'Outputs have appropriate properties'
description = 'Making sure the outputs are properly configured'
source_url = 'https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/outputs-section-structure.html'
tags = ['outputs']
valid_keys = ['Value', 'Export', 'Description', 'Condition']
# Map can be singular or multiple for this case we are going to skip
valid_funcs = FUNCTIONS_SINGLE + ['Fn::FindInMap', 'Fn::Transform']
def check_func(self, value, path):
"""Check that a value is using the correct functions"""
matches = []
if isinstance(value, dict):
if len(value) == 1:
for k in value.keys():
if k not in self.valid_funcs:
message = '{0} must use one of the functions {1}'
matches.append(
RuleMatch(
path, message.format('/'.join(path), self.valid_funcs)
)
)
else:
message = '{0} must use one of the functions {1}'
matches.append(
RuleMatch(path, message.format('/'.join(path), self.valid_funcs))
)
elif isinstance(value, (list)):
message = '{0} must be a string or one of the functions {1}'
matches.append(
RuleMatch(path, message.format('/'.join(path), self.valid_funcs))
)
return matches
def check_export(self, value, path):
"""Check export structure"""
matches = []
if isinstance(value, dict):
if len(value) == 1:
for k, v in value.items():
if k != 'Name':
message = '{0} must be a an object of one with key "Name"'
matches.append(RuleMatch(path, message.format('/'.join(path))))
else:
matches.extend(self.check_func(v, path[:] + ['Name']))
else:
message = '{0} must be a an object of one with key "Name"'
matches.append(RuleMatch(path, message.format('/'.join(path))))
else:
message = '{0} must be a an object of one with key "Name"'
matches.append(RuleMatch(path, message.format('/'.join(path))))
return matches
def match(self, cfn):
matches = []
outputs = cfn.template.get('Outputs', {})
if outputs:
if isinstance(outputs, dict):
for output_name, output_value in outputs.items():
if not isinstance(output_value, dict):
message = 'Output {0} is not an object'
matches.append(
RuleMatch(
['Outputs', output_name], message.format(output_name)
)
)
else:
for propname, propvalue in output_value.items():
if propname not in self.valid_keys:
message = 'Output {0} has invalid property {1}'
matches.append(
RuleMatch(
['Outputs', output_name, propname],
message.format(output_name, propname),
)
)
else:
if propvalue is None:
message = (
'Output {0} has property {1} has invalid type'
)
matches.append(
RuleMatch(
['Outputs', output_name, propname],
message.format(output_name, propname),
)
)
value = output_value.get('Value')
if value:
matches.extend(
self.check_func(
value, ['Outputs', output_name, 'Value']
)
)
export = output_value.get('Export')
if export:
matches.extend(
self.check_export(
export, ['Outputs', output_name, 'Export']
)
)
else:
matches.append(
RuleMatch(['Outputs'], 'Outputs do not follow correct format.')
)
return matches