Repository: cloud-custodian/cloud-custodian

Issue:
aws - Pinpoint resource support
### Describe the feature

Support for AWS resource `Pinpoint`

Goal: 
- Pinpoint apps should be tagged

### Extra information or context

_No response_


Discussion:

Resolving patch:
diff --git a/c7n/resources/pinpoint.py b/c7n/resources/pinpoint.py
new file mode 100644
--- /dev/null
+++ b/c7n/resources/pinpoint.py
@@ -0,0 +1,26 @@
+# Copyright The Cloud Custodian Authors.
+# SPDX-License-Identifier: Apache-2.0
+
+from c7n.manager import resources
+from c7n.query import QueryResourceManager, TypeInfo
+
+
+@resources.register('pinpoint-app')
+class PinpointApp(QueryResourceManager):
+    class resource_type(TypeInfo):
+        service = 'pinpoint'
+        arn_type = 'apps'
+        enum_spec = ('get_apps', 'ApplicationsResponse.Item', None)
+        name = "Name"
+        id = 'Id'
+        universal_taggable = True
+        cfn_type = 'AWS::Pinpoint::App'
+        arn = "Arn"
+        permission_prefix = 'mobiletargeting'
+
+    def augment(self, resources):
+        for resource in resources:
+            tags = resource.get('tags', {})
+            formatted_tags = [{'Key': k, 'Value': v} for k, v in tags.items()]
+            resource['Tags'] = formatted_tags
+        return resources
diff --git a/c7n/resources/resource_map.py b/c7n/resources/resource_map.py
--- a/c7n/resources/resource_map.py
+++ b/c7n/resources/resource_map.py
@@ -155,6 +155,7 @@
     "aws.opswork-stack": "c7n.resources.opsworks.OpsworkStack",
     "aws.prefix-list": "c7n.resources.vpc.PrefixList",
     "aws.peering-connection": "c7n.resources.vpc.PeeringConnection",
+    "aws.pinpoint-app": "c7n.resources.pinpoint.PinpointApp",
     'aws.qldb': 'c7n.resources.qldb.QLDB',
     "aws.r53domain": "c7n.resources.route53.Route53Domain",
     "aws.rds": "c7n.resources.rds.RDS",


Test patch:
diff --git a/tests/data/placebo/test_pinpoint_app_remove_tag/pinpoint.GetApps_1.json b/tests/data/placebo/test_pinpoint_app_remove_tag/pinpoint.GetApps_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_pinpoint_app_remove_tag/pinpoint.GetApps_1.json
@@ -0,0 +1,19 @@
+{
+    "status_code": 200,
+    "data": {
+        "ResponseMetadata": {},
+        "ApplicationsResponse": {
+            "Item": [
+                {
+                    "Arn": "arn:aws:mobiletargeting:us-east-1:644160558196:apps/4bf4814446ed468aa80dce914c61a180",
+                    "Id": "4bf4814446ed468aa80dce914c61a180",
+                    "Name": "test_project",
+                    "tags": {
+                        "foo": "bar"
+                    },
+                    "CreationDate": "2023-04-14T14:11:48.738Z"
+                }
+            ]
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_pinpoint_app_remove_tag/pinpoint.ListTagsForResource_1.json b/tests/data/placebo/test_pinpoint_app_remove_tag/pinpoint.ListTagsForResource_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_pinpoint_app_remove_tag/pinpoint.ListTagsForResource_1.json
@@ -0,0 +1,9 @@
+{
+    "status_code": 200,
+    "data": {
+        "ResponseMetadata": {},
+        "TagsModel": {
+            "tags": {}
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_pinpoint_app_remove_tag/tagging.UntagResources_1.json b/tests/data/placebo/test_pinpoint_app_remove_tag/tagging.UntagResources_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_pinpoint_app_remove_tag/tagging.UntagResources_1.json
@@ -0,0 +1,7 @@
+{
+    "status_code": 200,
+    "data": {
+        "FailedResourcesMap": {},
+        "ResponseMetadata": {}
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_pinpoint_app_tag/pinpoint.GetApps_1.json b/tests/data/placebo/test_pinpoint_app_tag/pinpoint.GetApps_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_pinpoint_app_tag/pinpoint.GetApps_1.json
@@ -0,0 +1,17 @@
+{
+    "status_code": 200,
+    "data": {
+        "ResponseMetadata": {},
+        "ApplicationsResponse": {
+            "Item": [
+                {
+                    "Arn": "arn:aws:mobiletargeting:us-east-1:644160558196:apps/4bf4814446ed468aa80dce914c61a180",
+                    "Id": "4bf4814446ed468aa80dce914c61a180",
+                    "Name": "test_project",
+                    "tags": {},
+                    "CreationDate": "2023-04-14T14:11:48.738Z"
+                }
+            ]
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_pinpoint_app_tag/pinpoint.ListTagsForResource_1.json b/tests/data/placebo/test_pinpoint_app_tag/pinpoint.ListTagsForResource_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_pinpoint_app_tag/pinpoint.ListTagsForResource_1.json
@@ -0,0 +1,11 @@
+{
+    "status_code": 200,
+    "data": {
+        "ResponseMetadata": {},
+        "TagsModel": {
+            "tags": {
+                "foo": "bar"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_pinpoint_app_tag/tagging.TagResources_1.json b/tests/data/placebo/test_pinpoint_app_tag/tagging.TagResources_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_pinpoint_app_tag/tagging.TagResources_1.json
@@ -0,0 +1,7 @@
+{
+    "status_code": 200,
+    "data": {
+        "FailedResourcesMap": {},
+        "ResponseMetadata": {}
+    }
+}
\ No newline at end of file
diff --git a/tests/test_pinpoint.py b/tests/test_pinpoint.py
new file mode 100644
--- /dev/null
+++ b/tests/test_pinpoint.py
@@ -0,0 +1,57 @@
+# Copyright The Cloud Custodian Authors.
+# SPDX-License-Identifier: Apache-2.0
+from .common import BaseTest
+
+class PinpointApp(BaseTest):
+    
+    def test_pinpoint_app_tag(self):
+        session_factory = self.replay_flight_data('test_pinpoint_app_tag')
+        p = self.load_policy(
+            {
+                'name': 'test-pinpoint-app-tag',
+                'resource': 'pinpoint-app',
+                'filters': [
+                    {
+                        'tag:foo': 'absent', 
+                    }
+                ],
+                'actions': [
+                    {
+                        'type': 'tag',
+                        'tags': {'foo': 'bar'}
+                    }
+                ]
+            }, session_factory=session_factory
+        )
+        resources = p.run()
+        self.assertEqual(len(resources), 1)
+        client = session_factory().client('pinpoint')
+        tags = client.list_tags_for_resource(ResourceArn=resources[0]['Arn'])['TagsModel']['tags']
+        self.assertEqual(len(tags), 1)
+        self.assertEqual(tags, {'foo': 'bar'})
+
+
+    def test_pinpoint_app_remove_tag(self):
+        session_factory = self.replay_flight_data('test_pinpoint_app_remove_tag')
+        p = self.load_policy(
+            {
+                'name': 'test-pinpoint-app-remove-tag',
+                'resource': 'pinpoint-app',
+                'filters': [
+                    {
+                        'tag:foo': 'present', 
+                    }
+                ],
+                'actions': [
+                    {
+                        'type': 'remove-tag',
+                        'tags': ['foo']
+                    }
+                ]
+            }, session_factory=session_factory
+        )
+        resources = p.run()
+        self.assertEqual(len(resources), 1)
+        client = session_factory().client('pinpoint')
+        tags = client.list_tags_for_resource(ResourceArn=resources[0]['Arn'])['TagsModel']['tags']
+        self.assertEqual(len(tags), 0)
\ No newline at end of file