Repository: cloud-custodian/cloud-custodian

Issue:
Add ECR metrics filter support
### Describe the feature

ECR metrics are now available

https://aws.amazon.com/about-aws/whats-new/2022/01/amazon-ecr-monitor-repository-pull-statistics/

metric: RepositoryPullCount

### Extra information or context

https://docs.aws.amazon.com/AmazonECR/latest/userguide/ecr-repository-metrics.html


Discussion:

Resolving patch:
diff --git a/c7n/filters/metrics.py b/c7n/filters/metrics.py
--- a/c7n/filters/metrics.py
+++ b/c7n/filters/metrics.py
@@ -103,6 +103,7 @@ class MetricsFilter(Filter):
         'cloudsearch': 'AWS/CloudSearch',
         'dynamodb': 'AWS/DynamoDB',
         'ecs': 'AWS/ECS',
+        'ecr': 'AWS/ECR',
         'efs': 'AWS/EFS',
         'elasticache': 'AWS/ElastiCache',
         'ec2': 'AWS/EC2',
diff --git a/c7n/resources/ecr.py b/c7n/resources/ecr.py
--- a/c7n/resources/ecr.py
+++ b/c7n/resources/ecr.py
@@ -4,7 +4,7 @@
 
 from c7n.actions import RemovePolicyBase, Action, ModifyPolicyBase
 from c7n.exceptions import PolicyValidationError
-from c7n.filters import CrossAccountAccessFilter, Filter, ValueFilter
+from c7n.filters import CrossAccountAccessFilter, Filter, ValueFilter, MetricsFilter
 from c7n.manager import resources
 from c7n.query import (
     ConfigSource, DescribeSource, QueryResourceManager, TypeInfo,
@@ -40,6 +40,7 @@ class resource_type(TypeInfo):
         filter_name = 'repositoryNames'
         filter_type = 'list'
         config_type = cfn_type = 'AWS::ECR::Repository'
+        dimension = 'RepositoryName'
 
     source_mapping = {
         'describe': DescribeECR,
@@ -47,6 +48,12 @@ class resource_type(TypeInfo):
     }
 
 
+@ECR.filter_registry.register('metrics')
+class ECRMetricsFilter(MetricsFilter):
+    def get_dimensions(self, resource):
+        return [{"Name": "RepositoryName", "Value": resource['repositoryName']}]
+
+
 class ECRImageQuery(ChildResourceQuery):
 
     def get(self, resource_manager, identities):


Test patch:
diff --git a/tests/data/placebo/test_ecr_metrics_filter/api.ecr.DescribeRepositories_1.json b/tests/data/placebo/test_ecr_metrics_filter/api.ecr.DescribeRepositories_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_ecr_metrics_filter/api.ecr.DescribeRepositories_1.json
@@ -0,0 +1,31 @@
+{
+    "status_code": 200,
+    "data": {
+        "repositories": [
+            {
+                "repositoryArn": "arn:aws:ecr:us-east-1:644160558196:repository/foo",
+                "registryId": "644160558196",
+                "repositoryName": "foo",
+                "repositoryUri": "644160558196.dkr.ecr.us-east-1.amazonaws.com/foo",
+                "createdAt": {
+                    "__class__": "datetime",
+                    "year": 2022,
+                    "month": 3,
+                    "day": 29,
+                    "hour": 16,
+                    "minute": 22,
+                    "second": 40,
+                    "microsecond": 0
+                },
+                "imageTagMutability": "MUTABLE",
+                "imageScanningConfiguration": {
+                    "scanOnPush": true
+                },
+                "encryptionConfiguration": {
+                    "encryptionType": "AES256"
+                }
+            }
+        ],
+        "ResponseMetadata": {}
+    }
+}
diff --git a/tests/data/placebo/test_ecr_metrics_filter/api.ecr.ListTagsForResource_1.json b/tests/data/placebo/test_ecr_metrics_filter/api.ecr.ListTagsForResource_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_ecr_metrics_filter/api.ecr.ListTagsForResource_1.json
@@ -0,0 +1,8 @@
+{
+    "status_code": 200,
+    "data": {
+        "tags": [
+        ],
+        "ResponseMetadata": {}
+    }
+}
diff --git a/tests/data/placebo/test_ecr_metrics_filter/monitoring.GetMetricStatistics_1.json b/tests/data/placebo/test_ecr_metrics_filter/monitoring.GetMetricStatistics_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_ecr_metrics_filter/monitoring.GetMetricStatistics_1.json
@@ -0,0 +1,33 @@
+{
+    "status_code": 200,
+    "data": {
+        "Label": "RepositoryPullCount",
+        "Datapoints": [
+            {
+                "Timestamp": {
+                    "__class__": "datetime",
+                    "year": 2022,
+                    "month": 8,
+                    "day": 29,
+                    "hour": 0,
+                    "minute": 14,
+                    "second": 0,
+                    "microsecond": 0
+                },
+                "Sum": 50,
+                "Unit": "Count"
+            }
+        ],
+        "ResponseMetadata": {
+            "RequestId": "34a04417-fa52-11e7-917a-f7a6d7e3d98b",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "x-amzn-requestid": "34a04417-fa52-11e7-917a-f7a6d7e3d98b",
+                "content-type": "text/xml",
+                "content-length": "515",
+                "date": "Mon, 29 Aug 2022 00:14:23 GMT"
+            },
+            "RetryAttempts": 0
+        }
+    }
+}
diff --git a/tests/test_ecr.py b/tests/test_ecr.py
--- a/tests/test_ecr.py
+++ b/tests/test_ecr.py
@@ -387,3 +387,26 @@ def test_ecr_repo_modify_policy(self):
             session_factory=session_factory)
         resources = p.run()
         self.assertEqual(len(resources), 1)
+
+    def test_ecr_metrics_filter(self):
+        session_factory = self.replay_flight_data("test_ecr_metrics_filter")
+        p = self.load_policy(
+            {
+                "name": "ecr-metrics-filter",
+                "resource": "aws.ecr",
+                "filters": [
+                    {
+                        "type": "metrics",
+                        "statistics": "Sum",
+                        "days": 5,
+                        "period": 86400,
+                        "op": "greater-than",
+                        "value": 1,
+                        "name": "RepositoryPullCount"
+                    }
+                ]
+            },
+            session_factory=session_factory
+        )
+        resources = p.run()
+        self.assertEqual(len(resources), 1)