forked from aws-cloudformation/cfn-lint
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathNestedStackParameters.py
More file actions
136 lines (121 loc) · 5.61 KB
/
NestedStackParameters.py
File metadata and controls
136 lines (121 loc) · 5.61 KB
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
"""
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
SPDX-License-Identifier: MIT-0
"""
import os
import regex as re
from cfnlint._typing import RuleMatches
from cfnlint.decode import decode
from cfnlint.helpers import REGEX_DYN_REF
from cfnlint.rules import CloudFormationLintRule, RuleMatch
from cfnlint.template.template import Template
class NestedStackParameters(CloudFormationLintRule):
"""Check that nested stack parameters are specified as needed"""
id = "E3043"
shortdesc = "Validate parameters for in a nested stack"
description = (
"Evalute if parameters for a nested stack are specified and "
"if parameters are specified for a nested stack that aren't required."
)
source_url = "https://github.com/aws-cloudformation/cfn-lint"
tags = ["resources", "cloudformation"]
def __init__(self):
"""Init"""
super().__init__()
self.resource_property_types.append("AWS::CloudFormation::Stack")
def __get_template_parameters(self, filename):
try:
(tmp, matches) = decode(filename)
except: # noqa: E722
return None
if matches:
return None
return tmp.get("Parameters", {})
def __compare_objects(self, template_parameters, nested_parameters, scenario, path):
matches = []
for key in set(template_parameters.keys()) - set(nested_parameters.keys()):
if scenario is None:
message = (
'Specified parameter "{0}" doesn\'t exist in nested stack template'
" at {1}"
)
matches.append(
RuleMatch(
path + [key],
message.format(key, "/".join(map(str, path + [key]))),
)
)
else:
message = (
'Specified parameter "{0}" doesn\'t exist in nested stack'
" template {1}"
)
scenario_text = " and ".join(
[f'when condition "{k}" is {v}' for (k, v) in scenario.items()]
)
matches.append(RuleMatch(path, message.format(key, scenario_text)))
for key in set(nested_parameters.keys()) - set(template_parameters.keys()):
if nested_parameters.get(key).get("Default") is None:
if scenario is None:
message = (
'Nested stack template parameter "{0}" is not specified at {1}'
)
matches.append(
RuleMatch(path, message.format(key, "/".join(map(str, path))))
)
else:
message = (
'Nested stack template parameter "{0}" is not specified {1}'
)
scenario_text = " and ".join(
[f'when condition "{k}" is {v}' for (k, v) in scenario.items()]
)
matches.append(RuleMatch(path, message.format(key, scenario_text)))
return matches
def match(self, cfn: Template) -> RuleMatches:
"""Check CloudFormation Properties"""
matches = []
resources = cfn.get_resources("AWS::CloudFormation::Stack")
for resource_name, attributes in resources.items():
properties = attributes.get("Properties", {})
# when template is passed via cat (or equivalent filename is none)
if cfn.filename:
base_dir = os.path.dirname(os.path.abspath(cfn.filename))
parameter_groups = cfn.get_object_without_conditions(
obj=properties, property_names=["TemplateURL", "Parameters"]
)
for parameter_group in parameter_groups:
obj = parameter_group.get("Object")
template_url = obj.get("TemplateURL")
if isinstance(template_url, str):
if not (
template_url.startswith("http://")
or template_url.startswith("https://")
or template_url.startswith("s3://")
):
template_path = os.path.normpath(
os.path.join(base_dir, template_url)
)
if re.match(REGEX_DYN_REF, template_path):
continue
nested_parameters = self.__get_template_parameters(
template_path
)
template_parameters = obj.get("Parameters")
if isinstance(nested_parameters, dict) and isinstance(
template_parameters, dict
):
matches.extend(
self.__compare_objects(
template_parameters=template_parameters,
nested_parameters=nested_parameters,
path=[
"Resources",
resource_name,
"Properties",
"Parameters",
],
scenario=parameter_group.get("Scenario"),
)
)
return matches