"""
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
SPDX-License-Identifier: MIT-0
"""
from cfnlint.rules import CloudFormationLintRule, RuleMatch
class Configuration(CloudFormationLintRule):
"""Check if Parameters are configured correctly"""
id = "E2001"
shortdesc = "Parameters have appropriate properties"
description = "Making sure the parameters are properly configured"
source_url = "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/parameters-section-structure.html"
tags = ["parameters"]
valid_keys = {
"AllowedPattern": {"Type": "String"},
"AllowedValues": {
"Type": "List",
"ItemType": "String",
},
"ConstraintDescription": {"Type": "String"},
"Default": {"Type": "String"},
"Description": {"Type": "String"},
"MaxLength": {
"Type": "Integer",
"ValidForTypes": [
"String",
"AWS::EC2::AvailabilityZone::Name",
"AWS::EC2::Image::Id",
"AWS::EC2::Instance::Id",
"AWS::EC2::KeyPair::KeyName",
"AWS::EC2::SecurityGroup::GroupName",
"AWS::EC2::SecurityGroup::Id",
"AWS::EC2::Subnet::Id",
"AWS::EC2::Volume::Id",
"AWS::EC2::VPC::Id",
"AWS::Route53::HostedZone::Id",
],
},
"MaxValue": {"Type": "Integer", "ValidForTypes": ["Number"]},
"MinLength": {
"Type": "Integer",
"ValidForTypes": [
"String",
"AWS::EC2::AvailabilityZone::Name",
"AWS::EC2::Image::Id",
"AWS::EC2::Instance::Id",
"AWS::EC2::KeyPair::KeyName",
"AWS::EC2::SecurityGroup::GroupName",
"AWS::EC2::SecurityGroup::Id",
"AWS::EC2::Subnet::Id",
"AWS::EC2::Volume::Id",
"AWS::EC2::VPC::Id",
"AWS::Route53::HostedZone::Id",
],
},
"MinValue": {"Type": "Integer", "ValidForTypes": ["Number"]},
"NoEcho": {"Type": "Boolean"},
"Type": {"Type": "String"},
}
required_keys = ["Type"]
def check_type(self, value, path, props):
"""Check the type and handle recursion with lists"""
results = []
prop_type = props.get("Type")
if value is None:
message = (
f'Property {"/".join(map(str, path))} should be of type {prop_type}'
)
results.append(RuleMatch(path, message))
return results
try:
if prop_type in ["List"]:
if isinstance(value, list):
for i, item in enumerate(value):
results.extend(
self.check_type(
item, path[:] + [i], {"Type": props.get("ItemType")}
)
)
else:
message = f'Property {"/".join(map(str, path))} should be of type {prop_type}'
results.append(RuleMatch(path, message))
if prop_type in ["String"]:
if isinstance(value, (dict, list)):
message = f'Property {"/".join(map(str, path))} should be of type {prop_type}'
results.append(RuleMatch(path, message))
str(value)
elif prop_type in ["Boolean"]:
if not isinstance(value, bool):
if value not in ["True", "true", "False", "false"]:
message = f'Property {"/".join(map(str, path))} should be of type {prop_type}'
results.append(RuleMatch(path, message))
elif prop_type in ["Integer"]:
if isinstance(value, bool):
message = f'Property {"/".join(map(str, path))} should be of type {prop_type}'
results.append(RuleMatch(path, message))
else: # has to be a Double
int(value)
except Exception: # pylint: disable=W0703
message = (
f'Property {"/".join(map(str, path))} should be of type {prop_type}'
)
results.append(
RuleMatch(
path,
message,
)
)
return results
def match(self, cfn):
matches = []
for paramname, paramvalue in cfn.get_parameters().items():
if isinstance(paramvalue, dict):
for propname, propvalue in paramvalue.items():
if propname not in self.valid_keys:
message = "Parameter {0} has invalid property {1}"
matches.append(
RuleMatch(
["Parameters", paramname, propname],
message.format(paramname, propname),
)
)
else:
props = self.valid_keys.get(propname)
prop_path = ["Parameters", paramname, propname]
matches.extend(self.check_type(propvalue, prop_path, props))
# Check that the property is needed for the current type
valid_for = props.get("ValidForTypes")
if valid_for is not None:
if paramvalue.get("Type") not in valid_for:
message = "Parameter {0} has property {1} which is only valid for {2}"
matches.append(
RuleMatch(
["Parameters", paramname, propname],
message.format(paramname, propname, valid_for),
)
)
for reqname in self.required_keys:
if reqname not in paramvalue.keys():
message = "Parameter {0} is missing required property {1}"
matches.append(
RuleMatch(
["Parameters", paramname],
message.format(paramname, reqname),
)
)
else:
message = "Parameter {0} is not an object"
matches.append(
RuleMatch(
["Parameters", paramname], message.format(paramname, reqname)
)
)
return matches