Repository: aws-cloudformation/cfn-lint

Issue:
E2520 false positive for CloudWatch Alarm with expression
### CloudFormation Lint Version

0.80.3

### What operating system are you using?

MacOS

### Describe the bug

A valid CloudWatch alarm that uses a metrics expression is resulting in an E2520 false positive.  The alarm was defined in the CloudWatch console and exported via the "View Source | CloudFormation YAML" capability, so it's definitionally a valid CloudWatch alarm.  To confirm that the bug isn't in the console, created a copy of the alarm using the generated definition and neither CloudFormation nor CloudWatch have any complaints.

### Expected behavior

E2520 should not be raised when `Dimensions` is present under `MetricStat.Metric`.

### Reproduction template

```yaml
AWSTemplateFormatVersion: "2010-09-09"

Description: AXIS ALB alarms

Parameters:
  pLoadBalancerId:
    Type: String
    Default: app/private-api-proxy/ced2a65499b104e7

  pAlarmPrefix:
    Type: String
    Default: MySampleApp

Resources:
  rAlb5xxPercentage:
    Type: AWS::CloudWatch::Alarm
    Properties:
        AlarmName: !Sub "${pAlarmPrefix}-ALB-5XX-Percentage"
        AlarmDescription: >-
            This alarm fires when the ALB is returning HTTP 5XX errors.  It is
            usually due to a misconfiguration of the ALB or not having any
            associated targets.


            See [runbook](https://google.com) for more details.
        ActionsEnabled: true
        OKActions: []
        AlarmActions: []
        InsufficientDataActions: []
        Dimensions: []
        EvaluationPeriods: 15
        DatapointsToAlarm: 3
        Threshold: 5
        ComparisonOperator: GreaterThanOrEqualToThreshold
        TreatMissingData: notBreaching
        Metrics:
          - Id: e1
            Label: ALB 5XX Percentage
            ReturnData: true
            Expression: (m2/(m1+m2+m3+0.001))*100
          - Id: m1
            ReturnData: false
            MetricStat:
              Metric:
                Namespace: AWS/ApplicationELB
                MetricName: RequestCount
                Dimensions:
                  - Name: LoadBalancer
                    Value: !Ref pLoadBalancerId
              Period: 60
              Stat: Sum
          - Id: m2
            ReturnData: false
            MetricStat:
              Metric:
                Namespace: AWS/ApplicationELB
                MetricName: HTTPCode_ELB_5XX_Count
                Dimensions:
                  - Name: LoadBalancer
                    Value: !Ref pLoadBalancerId
              Period: 60
              Stat: Sum
          - Id: m3
            ReturnData: false
            MetricStat:
              Metric:
                Namespace: AWS/ApplicationELB
                MetricName: HTTPCode_ELB_4XX_Count
                Dimensions:
                  - Name: LoadBalancer
                    Value: !Ref pLoadBalancerId
              Period: 60
              Stat: Sum
```


Discussion:

Resolving patch:
diff --git a/src/cfnlint/rules/resources/properties/Exclusive.py b/src/cfnlint/rules/resources/properties/Exclusive.py
--- a/src/cfnlint/rules/resources/properties/Exclusive.py
+++ b/src/cfnlint/rules/resources/properties/Exclusive.py
@@ -40,7 +40,7 @@ def check(self, properties, exclusions, path, cfn):
                     for prop in obj:
                         if prop == k:
                             for excl_property in exclusions[prop]:
-                                if excl_property in obj:
+                                if obj.get(excl_property):
                                     if property_set["Scenario"] is None:
                                         message = "Property {0} should NOT exist with {1} for {2}"
                                         matches.append(


Test patch:
diff --git a/test/fixtures/templates/good/resources/properties/exclusive.yaml b/test/fixtures/templates/good/resources/properties/exclusive.yaml
--- a/test/fixtures/templates/good/resources/properties/exclusive.yaml
+++ b/test/fixtures/templates/good/resources/properties/exclusive.yaml
@@ -5,4 +5,47 @@ Resources:
       SourceSecurityGroupId: sg-abc12345
       CidrIp: !Ref AWS::NoValue
       IpProtocol: "-1"
-      GroupId: sg-abc1234567
\ No newline at end of file
+      GroupId: sg-abc1234567
+  Alarm:
+    Type: AWS::CloudWatch::Alarm
+    Properties:
+      AlarmName: !Sub "${AWS::StackName}-ALB-5XX-Percentage"
+      ActionsEnabled: true
+      OKActions: []
+      AlarmActions: []
+      InsufficientDataActions: []
+      Dimensions: []
+      EvaluationPeriods: 15
+      DatapointsToAlarm: 3
+      Threshold: 5
+      ComparisonOperator: GreaterThanOrEqualToThreshold
+      TreatMissingData: notBreaching
+      Metrics:
+        - Id: e1
+          Label: ALB 5XX Percentage
+          ReturnData: true
+          Expression: (m2/(m1+m2+m3+0.001))*100
+        - Id: m1
+          ReturnData: false
+          MetricStat:
+            Metric:
+              Namespace: AWS/ApplicationELB
+              MetricName: RequestCount
+            Period: 60
+            Stat: Sum
+        - Id: m2
+          ReturnData: false
+          MetricStat:
+            Metric:
+              Namespace: AWS/ApplicationELB
+              MetricName: HTTPCode_ELB_5XX_Count
+            Period: 60
+            Stat: Sum
+        - Id: m3
+          ReturnData: false
+          MetricStat:
+            Metric:
+              Namespace: AWS/ApplicationELB
+              MetricName: HTTPCode_ELB_4XX_Count
+            Period: 60
+            Stat: Sum
diff --git a/test/unit/rules/resources/properties/test_exclusive.py b/test/unit/rules/resources/properties/test_exclusive.py
--- a/test/unit/rules/resources/properties/test_exclusive.py
+++ b/test/unit/rules/resources/properties/test_exclusive.py
@@ -16,6 +16,9 @@ def setUp(self):
         """Setup"""
         super(TestPropertyExclusive, self).setUp()
         self.collection.register(Exclusive())
+        self.success_templates = [
+            "test/fixtures/templates/good/resources/properties/exclusive.yaml"
+        ]
 
     def test_file_positive(self):
         """Test Positive"""