forked from aws-cloudformation/cfn-lint
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathGetAtt.py
148 lines (134 loc) · 6.08 KB
/
GetAtt.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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
"""
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
SPDX-License-Identifier: MIT-0
"""
import logging
from cfnlint.rules import CloudFormationLintRule
from cfnlint.rules import RuleMatch
import cfnlint.helpers
LOGGER = logging.getLogger('cfnlint')
class GetAtt(CloudFormationLintRule):
"""Check if GetAtt values are correct"""
id = 'E1010'
shortdesc = 'GetAtt validation of parameters'
description = 'Validates that GetAtt parameters are to valid resources and properties of those resources'
source_url = 'https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-getatt.html'
tags = ['functions', 'getatt']
def __init__(self):
"""Init"""
super().__init__()
self.propertytypes = []
self.resourcetypes = []
def initialize(self, cfn):
resourcespecs = cfnlint.helpers.RESOURCE_SPECS[cfn.regions[0]]
self.resourcetypes = resourcespecs['ResourceTypes']
self.propertytypes = resourcespecs['PropertyTypes']
def match(self, cfn):
matches = []
getatts = cfn.search_deep_keys('Fn::GetAtt')
valid_getatts = cfn.get_valid_getatts()
valid_attribute_functions = ['Ref']
for getatt in getatts:
if len(getatt[-1]) < 2:
message = 'Invalid GetAtt for {0}'
matches.append(
RuleMatch(getatt, message.format('/'.join(map(str, getatt[:-1]))))
)
continue
if isinstance(getatt[-1], str):
resname, restype = getatt[-1].split('.', 1)
else:
resname = None
restype = None
if isinstance(getatt[-1][1], str):
resname = getatt[-1][0]
restype = '.'.join(getatt[-1][1:])
elif isinstance(getatt[-1][1], dict):
# You can ref the secondary part of a getatt
resname = getatt[-1][0]
restype = getatt[-1][1]
if len(restype) == 1:
for k in restype.keys():
if k not in valid_attribute_functions:
message = 'GetAtt only supports functions "{0}" for attributes at {1}'
matches.append(
RuleMatch(
getatt,
message.format(
', '.join(
map(str, valid_attribute_functions)
),
'/'.join(map(str, getatt[:-1])),
),
)
)
else:
message = 'Invalid GetAtt structure {0} at {1}'
matches.append(
RuleMatch(
getatt,
message.format(
getatt[-1], '/'.join(map(str, getatt[:-1]))
),
)
)
# setting restype to None as we can't validate that anymore
restype = None
else:
message = 'Invalid GetAtt structure {0} at {1}'
matches.append(
RuleMatch(
getatt,
message.format(getatt[-1], '/'.join(map(str, getatt[:-1]))),
)
)
# only check resname if its set. if it isn't it is because of bad structure
# and an error is already provided
if resname:
if resname in valid_getatts:
if restype is not None:
# Check for maps
restypeparts = restype.split('.')
if (
restypeparts[0] in valid_getatts[resname]
and len(restypeparts) >= 2
):
if (
valid_getatts[resname][restypeparts[0]].get('Type')
!= 'Map'
):
message = 'Invalid GetAtt {0}.{1} for resource {2}'
matches.append(
RuleMatch(
getatt[:-1],
message.format(resname, restype, getatt[1]),
)
)
else:
continue
if (
restype not in valid_getatts[resname]
and '*' not in valid_getatts[resname]
):
message = 'Invalid GetAtt {0}.{1} for resource {2}'
matches.append(
RuleMatch(
getatt[:-1],
message.format(resname, restype, getatt[1]),
)
)
else:
modules = cfn.get_modules()
if any(resname.startswith(s) for s in modules.keys()):
LOGGER.debug(
'Will consider valid getatt %s because it references a resource within a module',
resname,
)
else:
message = 'Invalid GetAtt {0}.{1} for resource {2}'
matches.append(
RuleMatch(
getatt, message.format(resname, restype, getatt[1])
)
)
return matches