"""
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
SPDX-License-Identifier: MIT-0
"""
import regex as re
from cfnlint.helpers import REGEX_ALPHANUMERIC
from cfnlint.rules import CloudFormationLintRule, RuleMatch
class KeyName(CloudFormationLintRule):
"""Check if Mapping Keys are type string"""
id = "E7003"
shortdesc = "Mapping keys are strings and alphanumeric"
description = (
"Check if Mappings keys are properly typed as strings and alphanumeric"
)
source_url = "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/mappings-section-structure.html"
tags = ["mappings"]
def check_attribute(self, key, path):
"""Check the key name for string and alphanumeric"""
matches = []
if not isinstance(key, str):
message = "Mapping attribute ({0}) has to be a string."
matches.append(RuleMatch(path[:], message.format(key)))
elif not re.match(REGEX_ALPHANUMERIC, key):
message = (
"Mapping attribute ({0}) has invalid name. Name has to be alphanumeric."
)
matches.append(RuleMatch(path[:], message.format(key)))
return matches
def check_key(self, key, path):
"""Check the key name for string and alphanumeric"""
matches = []
if not isinstance(key, str):
message = "Mapping key ({0}) has to be a string."
matches.append(RuleMatch(path[:], message.format(key)))
elif not re.match("^[a-zA-Z0-9.-]{1,255}$", key) and key != "Fn::Transform":
message = "Mapping key ({0}) has invalid name. Name has to be alphanumeric, '-' or '.'"
matches.append(RuleMatch(path[:], message.format(key)))
return matches
def match(self, cfn):
matches = []
mappings = cfn.template.get("Mappings", {})
for mapping_name, mapping_value in mappings.items():
if isinstance(mapping_value, dict):
for key_name, key_value in mapping_value.items():
matches.extend(
self.check_key(key_name, ["Mappings", mapping_name, key_name])
)
if isinstance(key_value, dict):
for sub_key_name, _ in key_value.items():
matches.extend(
self.check_attribute(
sub_key_name,
["Mappings", mapping_name, key_name, sub_key_name],
)
)
return matches