"""
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
SPDX-License-Identifier: MIT-0
"""
import regex as re
from cfnlint.rules import CloudFormationLintRule, RuleMatch
class Default(CloudFormationLintRule):
"""Check if Parameters are configured correctly"""
id = "E2015"
shortdesc = "Default value is within parameter constraints"
description = "Making sure the parameters have a default value inside AllowedValues, MinValue, MaxValue, AllowedPattern"
source_url = "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/parameters-section-structure.html"
tags = ["parameters"]
def check_allowed_pattern(self, allowed_value, allowed_pattern, path):
"""
Check allowed value against allowed pattern
"""
message = "Default should be allowed by AllowedPattern"
try:
if not re.match(allowed_pattern, str(allowed_value)):
return [RuleMatch(path, message)]
except re.error as ex:
self.logger.debug(
'Regex pattern "%s" isn\'t supported by Python: %s', allowed_pattern, ex
)
return []
def check_min_value(self, allowed_value, min_value, path):
"""
Check allowed value against min value
"""
message = "Default should be equal to or higher than MinValue"
if isinstance(allowed_value, int) and isinstance(min_value, int):
if allowed_value < min_value:
return [RuleMatch(path, message)]
return []
def check_max_value(self, allowed_value, max_value, path):
"""
Check allowed value against max value
"""
message = "Default should be less than or equal to MaxValue"
if isinstance(allowed_value, int) and isinstance(max_value, int):
if allowed_value > max_value:
return [RuleMatch(path, message)]
return []
def check_allowed_values(self, allowed_value, allowed_values, path):
"""
Check allowed value against allowed values
"""
message = "Default should be a value within AllowedValues"
if allowed_value not in allowed_values:
return [RuleMatch(path, message)]
return []
def check_min_length(self, allowed_value, min_length, path):
"""
Check allowed value against MinLength
"""
message = "Default should have a length above or equal to MinLength"
value = allowed_value if isinstance(allowed_value, str) else str(allowed_value)
if isinstance(min_length, int):
if len(value) < min_length:
return [RuleMatch(path, message)]
return []
def check_max_length(self, allowed_value, max_length, path):
"""
Check allowed value against MaxLength
"""
message = "Default should have a length below or equal to MaxLength"
value = allowed_value if isinstance(allowed_value, str) else str(allowed_value)
if isinstance(max_length, int):
if len(value) > max_length:
return [RuleMatch(path, message)]
return []
def match_default_value(self, paramname, paramvalue, default_value):
matches = []
if default_value is not None:
path = ["Parameters", paramname, "Default"]
allowed_pattern = paramvalue.get("AllowedPattern")
if allowed_pattern:
matches.extend(
self.check_allowed_pattern(default_value, allowed_pattern, path)
)
min_value = paramvalue.get("MinValue")
if min_value:
matches.extend(self.check_min_value(default_value, min_value, path))
max_value = paramvalue.get("MaxValue")
if max_value is not None:
matches.extend(self.check_max_value(default_value, max_value, path))
allowed_values = paramvalue.get("AllowedValues")
if allowed_values:
matches.extend(
self.check_allowed_values(default_value, allowed_values, path)
)
min_length = paramvalue.get("MinLength")
if min_length is not None:
matches.extend(self.check_min_length(default_value, min_length, path))
max_length = paramvalue.get("MaxLength")
if max_length is not None:
matches.extend(self.check_max_length(default_value, max_length, path))
return matches
def is_cdl(self, paramvalue):
return paramvalue.get("Type") == "CommaDelimitedList"
def match(self, cfn):
matches = []
for paramname, paramvalue in cfn.get_parameters_valid().items():
param_cdl_matches = []
param_matches = []
default_value = paramvalue.get("Default")
if default_value is not None and self.is_cdl(paramvalue):
comma_delimited_default_values = [
x.strip() for x in default_value.split(",")
]
for value in comma_delimited_default_values:
param_cdl_matches.extend(
self.match_default_value(paramname, paramvalue, value)
)
if param_cdl_matches or not self.is_cdl(paramvalue):
param_matches.extend(
self.match_default_value(paramname, paramvalue, default_value)
)
if param_matches:
matches.extend(param_matches)
matches.extend(param_cdl_matches)
return matches