Repository: aws-cloudformation/cfn-lint

Issue:
AWS::Logs::MetricFilter MetricValues permits invalid strings
`cfn-lint 0.30.1`

AWS::Logs::MetricFilter.Properties.MetricTransformations[*].MetricValue allows a bare string not starting with '$' which it appears is never actually valid, ie 
"MetricValue: length" vs "MetricValue: $length"

Assuming I'm reading the documentation correctly MetricValue must always either be a number OR start with a '$' character.

The following fragment lints, but is rejected by CloudFormation at runtime without a $ at the start of the named MetricValue field
```
    QueueLengthMetricFilter:
        Type: AWS::Logs::MetricFilter
        Properties:
            LogGroupName: !Ref LogGroup
            FilterPattern: '[date, time, tag="rh-sched*", x01=throttling, x02="jobs.", ..., x10=Len, x11=of, x12=job, x13="queue*",  length]'
            MetricTransformations:
              - MetricValue: length
                MetricNamespace: !Sub '${EnvironmentName}'
                MetricName: 'JobsQueued'
```


Note: I believe that this is also missed by the AWS ValidateTemplate API


Discussion:
["For example, if you're counting the occurrences of a particular term like Error, specify 1 for the metric value. If you're counting the number of bytes transferred, reference the value that is in the log event by using $ followed by the name of the field that you specified in the filter pattern, such as $size."](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-logs-metricfilter-metrictransformation.html#cfn-cwl-metricfilter-metrictransformation-metricvalue)

---

As described [here](https://github.com/aws-cloudformation/cfn-python-lint/issues/903#issuecomment-595356853), this would need an `AllowedPatternRegex` to be enforced:

https://github.com/aws-cloudformation/cfn-python-lint/blob/a46773c752247c51effef415bd3462eaec10ab0b/src/cfnlint/data/ExtendedSpecs/all/03_value_types.json#L33-L36
This is still an issue, would love to see it fixed.

Resolving patch:
diff --git a/src/cfnlint/rules/resources/properties/AllowedPattern.py b/src/cfnlint/rules/resources/properties/AllowedPattern.py
--- a/src/cfnlint/rules/resources/properties/AllowedPattern.py
+++ b/src/cfnlint/rules/resources/properties/AllowedPattern.py
@@ -3,6 +3,7 @@
 SPDX-License-Identifier: MIT-0
 """
 import re
+import six
 from cfnlint.rules import CloudFormationLintRule
 from cfnlint.rules import RuleMatch
 
@@ -34,18 +35,22 @@ def check_value(self, value, path, property_name, **kwargs):
         # the RegEx itself is used.
         value_pattern = kwargs.get('value_specs', {}).get('AllowedPattern', value_pattern_regex)
 
-        if value_pattern_regex:
-            regex = re.compile(value_pattern_regex)
+        if isinstance(value, (int, float)):
+            value = str(value)
 
-            # Ignore values with dynamic references. Simple check to prevent false-positives
-            # See: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/dynamic-references.html
-            if '{{resolve:' not in value:
-                if not regex.match(value):
-                    full_path = ('/'.join(str(x) for x in path))
+        if isinstance(value, six.string_types):
+            if value_pattern_regex:
+                regex = re.compile(value_pattern_regex)
 
-                    message = '{} contains invalid characters (Pattern: {}) at {}'
-                    matches.append(RuleMatch(path, message.format(
-                        property_name, value_pattern, full_path)))
+                # Ignore values with dynamic references. Simple check to prevent false-positives
+                # See: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/dynamic-references.html
+                if '{{resolve:' not in value:
+                    if not regex.match(value):
+                        full_path = ('/'.join(str(x) for x in path))
+
+                        message = '{} contains invalid characters (Pattern: {}) at {}'
+                        matches.append(RuleMatch(path, message.format(
+                            property_name, value_pattern, full_path)))
 
         return matches
 


Test patch: