Repository: cloud-custodian/cloud-custodian

Issue:
API gateway for tag compliance use cases
resourcegrouptagging api supports api-gateway, so it should be easy to support this resource type for tag compliance use cases.  


Discussion:

Resolving patch:
diff --git a/c7n/resources/apigw.py b/c7n/resources/apigw.py
--- a/c7n/resources/apigw.py
+++ b/c7n/resources/apigw.py
@@ -12,7 +12,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 from __future__ import absolute_import, division, print_function, unicode_literals
-
+import functools
 from botocore.exceptions import ClientError
 
 from concurrent.futures import as_completed
@@ -22,6 +22,8 @@
 from c7n.filters.iamaccess import CrossAccountAccessFilter
 from c7n.manager import resources, ResourceManager
 from c7n import query, utils
+from c7n.tags import register_universal_tags
+from c7n.utils import generate_arn
 
 
 ANNOTATION_KEY_MATCHED_METHODS = 'c7n:matched-resource-methods'
@@ -122,7 +124,7 @@ class RestApi(query.QueryResourceManager):
 
     class resource_type(query.TypeInfo):
         service = 'apigateway'
-        arn_type = 'restapis'
+        arn_type = '/restapis'
         enum_spec = ('get_rest_apis', 'items', None)
         id = 'id'
         name = 'name'
@@ -130,6 +132,21 @@ class resource_type(query.TypeInfo):
         dimension = 'GatewayName'
         config_type = "AWS::ApiGateway::RestApi"
 
+    @property
+    def generate_arn(self):
+        """
+         Sample arn: arn:aws:apigateway:us-east-1::/restapis/rest-api-id
+         This method overrides c7n.utils.generate_arn and drops
+         account id from the generic arn.
+        """
+        if self._generate_arn is None:
+            self._generate_arn = functools.partial(
+                generate_arn,
+                self.resource_type.service,
+                region=self.config.region,
+                resource_type=self.resource_type.arn_type)
+        return self._generate_arn
+
 
 @RestApi.filter_registry.register('cross-account')
 class RestApiCrossAccount(CrossAccountAccessFilter):
@@ -178,6 +195,9 @@ def process(self, resources):
                 patchOperations=self.data['patch'])
 
 
+register_universal_tags(RestApi.filter_registry, RestApi.action_registry, compatibility=False)
+
+
 @resources.register('rest-stage')
 class RestStage(query.ChildResourceManager):
 


Test patch:
diff --git a/tests/data/placebo/test_rest_api_tag_untag_mark/apigateway.GetRestApis_1.json b/tests/data/placebo/test_rest_api_tag_untag_mark/apigateway.GetRestApis_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_rest_api_tag_untag_mark/apigateway.GetRestApis_1.json
@@ -0,0 +1,43 @@
+{
+    "status_code": 200,
+    "data": {
+        "ResponseMetadata": {
+            "RequestId": "b06e46db-d963-4191-8f90-9687782e267b",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "date": "Mon, 09 Sep 2019 19:59:04 GMT",
+                "content-type": "application/json",
+                "content-length": "1160",
+                "connection": "keep-alive",
+                "x-amzn-requestid": "b06e46db-d963-4191-8f90-9687782e267b",
+                "x-amz-apigw-id": "fxBJXKOKIAMEdJQ="
+            },
+            "RetryAttempts": 0
+        },
+        "items": [
+            {
+                "id": "dj7uijzv27",
+                "name": "c7n-test-pratyush",
+                "createdDate": {
+                    "__class__": "datetime",
+                    "year": 2019,
+                    "month": 5,
+                    "day": 9,
+                    "hour": 0,
+                    "minute": 43,
+                    "second": 51,
+                    "microsecond": 0
+                },
+                "apiKeySource": "HEADER",
+                "endpointConfiguration": {
+                    "types": [
+                        "PRIVATE"
+                    ]
+                },
+                "tags": {
+                    "target-tag": "pratyush"
+                }
+            }
+        ]
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_rest_api_tag_untag_mark/apigateway.GetTags_1.json b/tests/data/placebo/test_rest_api_tag_untag_mark/apigateway.GetTags_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_rest_api_tag_untag_mark/apigateway.GetTags_1.json
@@ -0,0 +1,21 @@
+{
+    "status_code": 200,
+    "data": {
+        "ResponseMetadata": {
+            "RequestId": "76c1d7b4-0665-4c1a-b991-f0a19b454a10",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "date": "Mon, 09 Sep 2019 19:59:04 GMT",
+                "content-type": "application/json",
+                "content-length": "35",
+                "connection": "keep-alive",
+                "x-amzn-requestid": "76c1d7b4-0665-4c1a-b991-f0a19b454a10",
+                "x-amz-apigw-id": "fxBJTLXvIAMEbog="
+            },
+            "RetryAttempts": 0
+        },
+        "tags": {
+            "target-tag": "pratyush"
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_rest_api_tag_untag_mark/apigateway.GetTags_2.json b/tests/data/placebo/test_rest_api_tag_untag_mark/apigateway.GetTags_2.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_rest_api_tag_untag_mark/apigateway.GetTags_2.json
@@ -0,0 +1,22 @@
+{
+    "status_code": 200,
+    "data": {
+        "ResponseMetadata": {
+            "RequestId": "815061d9-be0a-4c5f-aba6-3d5fa03a18f1",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "date": "Mon, 09 Sep 2019 19:59:06 GMT",
+                "content-type": "application/json",
+                "content-length": "94",
+                "connection": "keep-alive",
+                "x-amzn-requestid": "815061d9-be0a-4c5f-aba6-3d5fa03a18f1",
+                "x-amz-apigw-id": "fxBJoLYGoAMEbog="
+            },
+            "RetryAttempts": 0
+        },
+        "tags": {
+            "Env": "Dev",
+            "custodian_cleanup": "Resource does not meet policy: update@2019/09/11"
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_rest_api_tag_untag_mark/tagging.TagResources_1.json b/tests/data/placebo/test_rest_api_tag_untag_mark/tagging.TagResources_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_rest_api_tag_untag_mark/tagging.TagResources_1.json
@@ -0,0 +1,17 @@
+{
+    "status_code": 200,
+    "data": {
+        "FailedResourcesMap": {},
+        "ResponseMetadata": {
+            "RequestId": "62021dab-d13f-4f15-8c47-ff5839f74a80",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "x-amzn-requestid": "62021dab-d13f-4f15-8c47-ff5839f74a80",
+                "content-type": "application/x-amz-json-1.1",
+                "content-length": "25",
+                "date": "Mon, 09 Sep 2019 19:59:05 GMT"
+            },
+            "RetryAttempts": 0
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_rest_api_tag_untag_mark/tagging.TagResources_2.json b/tests/data/placebo/test_rest_api_tag_untag_mark/tagging.TagResources_2.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_rest_api_tag_untag_mark/tagging.TagResources_2.json
@@ -0,0 +1,17 @@
+{
+    "status_code": 200,
+    "data": {
+        "FailedResourcesMap": {},
+        "ResponseMetadata": {
+            "RequestId": "ce58862d-64a7-4791-b318-3fcf19fdfc2e",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "x-amzn-requestid": "ce58862d-64a7-4791-b318-3fcf19fdfc2e",
+                "content-type": "application/x-amz-json-1.1",
+                "content-length": "25",
+                "date": "Mon, 09 Sep 2019 19:59:06 GMT"
+            },
+            "RetryAttempts": 0
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_rest_api_tag_untag_mark/tagging.UntagResources_1.json b/tests/data/placebo/test_rest_api_tag_untag_mark/tagging.UntagResources_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_rest_api_tag_untag_mark/tagging.UntagResources_1.json
@@ -0,0 +1,17 @@
+{
+    "status_code": 200,
+    "data": {
+        "FailedResourcesMap": {},
+        "ResponseMetadata": {
+            "RequestId": "e91b4b38-91a0-4ebd-b7f8-c912c9a9cc4d",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "x-amzn-requestid": "e91b4b38-91a0-4ebd-b7f8-c912c9a9cc4d",
+                "content-type": "application/x-amz-json-1.1",
+                "content-length": "25",
+                "date": "Mon, 09 Sep 2019 19:59:05 GMT"
+            },
+            "RetryAttempts": 0
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/test_apigw.py b/tests/test_apigw.py
--- a/tests/test_apigw.py
+++ b/tests/test_apigw.py
@@ -90,6 +90,34 @@ def test_rest_api_update(self):
             restApiId=resources[0]['id'])
         self.assertEqual(updated['description'], 'for replacement')
 
+    def test_rest_api_tag_untag_mark(self):
+        session_factory = self.replay_flight_data('test_rest_api_tag_untag_mark')
+        client = session_factory().client("apigateway")
+        tags = client.get_tags(resourceArn='arn:aws:apigateway:us-east-1::/restapis/dj7uijzv27')
+        self.assertEqual(tags.get('tags', {}),
+            {'target-tag': 'pratyush'})
+        self.maxDiff = None
+        p = self.load_policy({
+            'name': 'tag-rest-api',
+            'resource': 'rest-api',
+            'filters': [{'type': 'value', 'key': 'id', 'value': 'dj7uijzv27'}],
+            "actions": [
+                {'type': 'tag',
+                'tags': {'Env': 'Dev'}},
+                {'type': 'remove-tag',
+                'tags': ['target-tag']},
+                {'type': 'mark-for-op', 'tag': 'custodian_cleanup',
+                'op': 'update',
+                'days': 2}
+            ]},
+            session_factory=session_factory)
+        resources = p.run()
+        self.assertTrue(len(resources), 1)
+        tags = client.get_tags(resourceArn='arn:aws:apigateway:us-east-1::/restapis/dj7uijzv27')
+        self.assertEqual(tags.get('tags', {}),
+            {'Env': 'Dev',
+            'custodian_cleanup': 'Resource does not meet policy: update@2019/09/11'})
+
 
 class TestRestResource(BaseTest):
 
diff --git a/tests/test_policy.py b/tests/test_policy.py
--- a/tests/test_policy.py
+++ b/tests/test_policy.py
@@ -156,7 +156,8 @@ def test_resource_arn_override_generator(self):
             if arn_gen:
                 overrides.add(k)
 
-        overrides = overrides.difference(set(('account', 's3', 'hostedzone', 'log-group')))
+        overrides = overrides.difference(set(
+            ('account', 's3', 'hostedzone', 'log-group', 'rest-api')))
         if overrides:
             raise ValueError("unknown arn overrides in %s" % (", ".join(overrides)))