Repository: cloud-custodian/cloud-custodian

Issue:
aws.transit-gateway resource support [enhancement]
https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ec2.html#EC2.Client.describe_transit_gateways


Discussion:

Resolving patch:
diff --git a/c7n/query.py b/c7n/query.py
--- a/c7n/query.py
+++ b/c7n/query.py
@@ -156,7 +156,7 @@ def filter(self, resource_manager, **params):
         # Have to query separately for each parent's children.
         results = []
         for parent_id in parent_ids:
-            merged_params = dict(params, **{parent_key: parent_id})
+            merged_params = self.get_parent_parameters(params, parent_id, parent_key)
             subset = self._invoke_client_enum(
                 client, enum_op, merged_params, path, retry=self.manager.retry)
             if annotate_parent:
@@ -168,6 +168,9 @@ def filter(self, resource_manager, **params):
                 results.extend(subset)
         return results
 
+    def get_parent_parameters(self, params, parent_id, parent_key):
+        return dict(params, **{parent_key: parent_id})
+
 
 class QueryMeta(type):
 
diff --git a/c7n/resources/vpc.py b/c7n/resources/vpc.py
--- a/c7n/resources/vpc.py
+++ b/c7n/resources/vpc.py
@@ -1328,6 +1328,48 @@ def process(self, resources, event=None):
         return results
 
 
+@resources.register('transit-gateway')
+class TransitGateway(query.QueryResourceManager):
+
+    class resource_type(object):
+        service = 'ec2'
+        enum_spec = ('describe_transit_gateways', 'TransitGateways', None)
+        dimension = None
+        name = id = 'TransitGatewayId'
+        filter_name = 'TransitGatewayIds'
+        filter_type = 'list'
+
+
+class TransitGatewayAttachmentQuery(query.ChildResourceQuery):
+
+    def get_parent_parameters(self, params, parent_id, parent_key):
+        merged_params = dict(params)
+        merged_params.setdefault('Filters', []).append(
+            {'Name': parent_key, 'Values': [parent_id]})
+        return merged_params
+
+
+@query.sources.register('transit-attachment')
+class TransitAttachmentSource(query.ChildDescribeSource):
+
+    resource_query_factory = TransitGatewayAttachmentQuery
+
+
+@resources.register('transit-attachment')
+class TransitGatewayAttachment(query.ChildResourceManager):
+
+    child_source = 'transit-attachment'
+
+    class resource_type(object):
+        service = 'ec2'
+        enum_spec = ('describe_transit_gateway_attachments', 'TransitGatewayAttachments', None)
+        parent_spec = ('transit-gateway', 'transit-gateway-id', None)
+        dimension = None
+        name = id = 'TransitGatewayAttachmentId'
+        filter_name = None
+        filter_type = None
+
+
 @resources.register('peering-connection')
 class PeeringConnection(query.QueryResourceManager):
 


Test patch:
diff --git a/tests/data/placebo/test_transit_gateway_attachment_query/ec2.DescribeTransitGatewayAttachments_1.json b/tests/data/placebo/test_transit_gateway_attachment_query/ec2.DescribeTransitGatewayAttachments_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_transit_gateway_attachment_query/ec2.DescribeTransitGatewayAttachments_1.json
@@ -0,0 +1,48 @@
+{
+    "status_code": 200,
+    "data": {
+        "TransitGatewayAttachments": [
+            {
+                "TransitGatewayAttachmentId": "tgw-attach-0aca50c09e4248a85",
+                "TransitGatewayId": "tgw-05e7c047df9e9d1ce",
+                "TransitGatewayOwnerId": "644160558196",
+                "ResourceOwnerId": "644160558196",
+                "ResourceType": "vpc",
+                "ResourceId": "vpc-f1516b97",
+                "State": "available",
+                "Association": {
+                    "TransitGatewayRouteTableId": "tgw-rtb-0fa4bd52512d99611",
+                    "State": "associated"
+                },
+                "CreationTime": {
+                    "__class__": "datetime",
+                    "year": 2019,
+                    "month": 1,
+                    "day": 16,
+                    "hour": 16,
+                    "minute": 13,
+                    "second": 28,
+                    "microsecond": 0
+                },
+                "Tags": [
+                    {
+                        "Key": "Name",
+                        "Value": "dev"
+                    }
+                ]
+            }
+        ],
+        "ResponseMetadata": {
+            "RequestId": "1081eb1d-ca68-4082-bd6c-4797a05dc5cb",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "content-type": "text/xml;charset=UTF-8",
+                "transfer-encoding": "chunked",
+                "vary": "Accept-Encoding",
+                "date": "Wed, 16 Jan 2019 17:09:01 GMT",
+                "server": "AmazonEC2"
+            },
+            "RetryAttempts": 0
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_transit_gateway_attachment_query/ec2.DescribeTransitGateways_1.json b/tests/data/placebo/test_transit_gateway_attachment_query/ec2.DescribeTransitGateways_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_transit_gateway_attachment_query/ec2.DescribeTransitGateways_1.json
@@ -0,0 +1,52 @@
+{
+    "status_code": 200,
+    "data": {
+        "TransitGateways": [
+            {
+                "TransitGatewayId": "tgw-05e7c047df9e9d1ce",
+                "TransitGatewayArn": "arn:aws:ec2:us-east-1:644160558196:transit-gateway/tgw-05e7c047df9e9d1ce",
+                "State": "available",
+                "OwnerId": "644160558196",
+                "Description": "test",
+                "CreationTime": {
+                    "__class__": "datetime",
+                    "year": 2019,
+                    "month": 1,
+                    "day": 16,
+                    "hour": 16,
+                    "minute": 10,
+                    "second": 52,
+                    "microsecond": 0
+                },
+                "Options": {
+                    "AmazonSideAsn": 64512,
+                    "AutoAcceptSharedAttachments": "enable",
+                    "DefaultRouteTableAssociation": "enable",
+                    "AssociationDefaultRouteTableId": "tgw-rtb-0fa4bd52512d99611",
+                    "DefaultRouteTablePropagation": "enable",
+                    "PropagationDefaultRouteTableId": "tgw-rtb-0fa4bd52512d99611",
+                    "VpnEcmpSupport": "enable",
+                    "DnsSupport": "enable"
+                },
+                "Tags": [
+                    {
+                        "Key": "Name",
+                        "Value": "config-agg"
+                    }
+                ]
+            }
+        ],
+        "ResponseMetadata": {
+            "RequestId": "3573aae5-ade5-481a-88a0-6b8fcdf4bb75",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "content-type": "text/xml;charset=UTF-8",
+                "transfer-encoding": "chunked",
+                "vary": "Accept-Encoding",
+                "date": "Wed, 16 Jan 2019 17:09:00 GMT",
+                "server": "AmazonEC2"
+            },
+            "RetryAttempts": 0
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_transit_gateway_query/ec2.DescribeTransitGateways_1.json b/tests/data/placebo/test_transit_gateway_query/ec2.DescribeTransitGateways_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_transit_gateway_query/ec2.DescribeTransitGateways_1.json
@@ -0,0 +1,52 @@
+{
+    "status_code": 200,
+    "data": {
+        "TransitGateways": [
+            {
+                "TransitGatewayId": "tgw-05e7c047df9e9d1ce",
+                "TransitGatewayArn": "arn:aws:ec2:us-east-1:644160558196:transit-gateway/tgw-05e7c047df9e9d1ce",
+                "State": "available",
+                "OwnerId": "644160558196",
+                "Description": "test",
+                "CreationTime": {
+                    "__class__": "datetime",
+                    "year": 2019,
+                    "month": 1,
+                    "day": 16,
+                    "hour": 16,
+                    "minute": 10,
+                    "second": 52,
+                    "microsecond": 0
+                },
+                "Options": {
+                    "AmazonSideAsn": 64512,
+                    "AutoAcceptSharedAttachments": "enable",
+                    "DefaultRouteTableAssociation": "enable",
+                    "AssociationDefaultRouteTableId": "tgw-rtb-0fa4bd52512d99611",
+                    "DefaultRouteTablePropagation": "enable",
+                    "PropagationDefaultRouteTableId": "tgw-rtb-0fa4bd52512d99611",
+                    "VpnEcmpSupport": "enable",
+                    "DnsSupport": "enable"
+                },
+                "Tags": [
+                    {
+                        "Key": "Name",
+                        "Value": "config-agg"
+                    }
+                ]
+            }
+        ],
+        "ResponseMetadata": {
+            "RequestId": "5b574b2a-0c7d-4ce7-a17a-fbb94f6a9108",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "content-type": "text/xml;charset=UTF-8",
+                "transfer-encoding": "chunked",
+                "vary": "Accept-Encoding",
+                "date": "Wed, 16 Jan 2019 16:18:17 GMT",
+                "server": "AmazonEC2"
+            },
+            "RetryAttempts": 0
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/test_vpc.py b/tests/test_vpc.py
--- a/tests/test_vpc.py
+++ b/tests/test_vpc.py
@@ -571,6 +571,27 @@ def test_s3_cidr_network_acl_not_present(self):
         self.assertEqual(len(resources), 1)
 
 
+class TransitGatewayTest(BaseTest):
+
+    def test_tgw_query(self):
+        factory = self.replay_flight_data('test_transit_gateway_query')
+        p = self.load_policy({
+            'name': 'test-tgw',
+            'resource': 'transit-gateway'}, session_factory=factory)
+        resources = p.run()
+        self.assertEqual(len(resources), 1)
+        self.assertEqual(resources[0]['Description'], 'test')
+
+    def test_tgw_attachment(self):
+        factory = self.replay_flight_data('test_transit_gateway_attachment_query')
+        p = self.load_policy({
+            'name': 'test-tgw-att',
+            'resource': 'transit-attachment'}, session_factory=factory)
+        resources = p.run()
+        self.assertEqual(len(resources), 1)
+        self.assertEqual(resources[0]['ResourceId'], 'vpc-f1516b97')
+
+
 class NetworkInterfaceTest(BaseTest):
 
     def test_and_or_nest(self):