""" Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: MIT-0 """ import regex as re import cfnlint.helpers from cfnlint.data import AdditionalSpecs from cfnlint.rules import CloudFormationLintRule, RuleMatch class BasedOnValue(CloudFormationLintRule): """Generic rule for checking properties based on value""" spec_type = "" message = "" def __init__(self): super().__init__() basedonvalue = cfnlint.helpers.load_resource( AdditionalSpecs, "BasedOnValue.json" ) self.resource_types_specs = basedonvalue["ResourceTypes"] self.property_types_specs = basedonvalue["PropertyTypes"] for resource_type, resource_specs in self.resource_types_specs.items(): for based_on_specs in resource_specs.values(): for spec in based_on_specs: if spec.get(self.spec_type): self.resource_property_types.append(resource_type) for resource_type, resource_specs in self.property_types_specs.items(): for based_on_specs in resource_specs.values(): for spec in based_on_specs: if spec.get(self.spec_type): self.resource_sub_property_types.append(resource_type) def _check_value(self, value, spec, cfn): """Checks a value to see if it fits in the spec""" if isinstance(value, str): regex = spec.get("Regex") if regex: if re.match(spec.get("Regex"), value): return True elif isinstance(value, dict): if len(value) == 1: for k, v in value.items(): ref = spec.get("Ref") if ref: if k == "Ref": if isinstance(v, str): return ( cfn.template.get("Resources").get(v, {}).get("Type") in ref ) getatt = spec.get("GetAtt") if getatt: if k == "Fn::GetAtt": if isinstance(v, list): restype = ( cfn.template.get("Resources").get(v[0]).get("Type") ) if restype in getatt: return getatt.get(restype) == v[1] return False # pylint: disable=unused-argument def _check_prop(self, prop, scenario): return False def _check_obj(self, properties, specs, path, cfn): matches = [] for k, v in specs.items(): for s in v: spec_values = s.get(self.spec_type) if spec_values: property_set = [k] + spec_values scenarios = cfn.get_object_without_conditions( properties, property_names=property_set ) for scenario in scenarios: if self._check_value(scenario.get("Object").get(k), s, cfn): for property_name in spec_values: if self._check_prop(property_name, scenario): if scenario["Scenario"] is None: message = "When property '{0}' has its current value property '{1}' {2} at {3}" matches.append( RuleMatch( path, message.format( k, property_name, self.message, "/".join(map(str, path)), ), ) ) else: scenario_text = " and ".join( [ f'when condition "{k}" is {v}' for (k, v) in scenario[ "Scenario" ].items() ] ) message = "When property '{0}' has its current value property '{1}' {2} when {3}" matches.append( RuleMatch( path, message.format( k, property_name, self.message, scenario_text, ), ) ) return matches def match_resource_properties(self, properties, resource_type, path, cfn): """Check CloudFormation Properties""" matches = [] if properties is None: # covered under rule E3001. If there are required properties properties is required first return matches # Need to get this spec specs = self.resource_types_specs.get(resource_type) matches.extend(self._check_obj(properties, specs, path, cfn)) return matches def match_resource_sub_properties(self, properties, property_type, path, cfn): """Check CloudFormation Properties""" matches = [] if properties is None: # covered under rule E3001. If there are required properties properties is required first return matches # Need to get this spec specs = self.property_types_specs.get(property_type) matches.extend(self._check_obj(properties, specs, path, cfn)) return matches