Repository: cloud-custodian/cloud-custodian

Issue:
AWS RedShift Reserved Nodes
**Describe the solution you'd like**
Parity of aws.rds-reserved / aws.ec2-reserved with a new aws.redshift-reserved resource.


**Additional context**
I would like the ability to identify redshift reserved nodes in the same way as rds and ec2.


Discussion:
sounds good, are you interested in contributing support for it? 

Resolving patch:
diff --git a/c7n/resources/redshift.py b/c7n/resources/redshift.py
--- a/c7n/resources/redshift.py
+++ b/c7n/resources/redshift.py
@@ -988,3 +988,18 @@ def process(self, snapshots):
                             ', '.join(
                                 [s['SnapshotIdentifier'] for s in futures[f]]),
                             f.exception()))
+
+
+@resources.register('redshift-reserved')
+class ReservedNode(QueryResourceManager):
+
+    class resource_type(TypeInfo):
+        service = 'redshift'
+        name = id = 'ReservedNodeId'
+        date = 'StartTime'
+        enum_spec = (
+            'describe_reserved_nodes', 'ReservedNodes', None)
+        filter_name = 'ReservedNodes'
+        filter_type = 'list'
+        arn_type = "reserved-nodes"
+        permissions_enum = ('redshift:DescribeReservedNodes',)
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
@@ -130,6 +130,7 @@
     "aws.redshift": "c7n.resources.redshift.Redshift",
     "aws.redshift-snapshot": "c7n.resources.redshift.RedshiftSnapshot",
     "aws.redshift-subnet-group": "c7n.resources.redshift.RedshiftSubnetGroup",
+    "aws.redshift-reserved": "c7n.resources.redshift.ReservedNode",
     "aws.rest-account": "c7n.resources.apigw.RestAccount",
     "aws.rest-api": "c7n.resources.apigw.RestApi",
     "aws.rest-resource": "c7n.resources.apigw.RestResource",


Test patch:
diff --git a/tests/data/placebo/test_redshift_reserved_node_query/redshift.DescribeReservedNodes_1.json b/tests/data/placebo/test_redshift_reserved_node_query/redshift.DescribeReservedNodes_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_redshift_reserved_node_query/redshift.DescribeReservedNodes_1.json
@@ -0,0 +1,28 @@
+{
+    "status_code": 200,
+    "data": {
+        "ReservedNodes": [
+            {
+                "ReservedNodeId": "1ba8e2e3-bc01-4d65-b35d-a4a3e931547e",
+                "ReservedNodeOfferingId": "ceb6a579-cf4c-4343-be8b-d832c45ab51c",
+                "NodeType": "dc2.xlarge",
+                "StartTime": "2020-06-07T11:08:39.051Z",
+                "Duration": 31536000,
+                "FixedPrice": 1380.0,
+                "UsagePrice": 0.0,
+                "CurrencyCode": "USD",
+                "NodeCount": 1,
+                "State": "active",
+                "OfferingType": "All Upfront",
+                "RecurringCharges": [
+                    {
+                        "RecurringChargeAmount": 0.0,
+                        "RecurringChargeFrequency": "Hourly"
+                    }
+                ],
+                "ReservedNodeOfferingType": "Regular"
+            }
+        ],
+        "ResponseMetadata": {}
+    }
+ }
diff --git a/tests/test_redshift.py b/tests/test_redshift.py
--- a/tests/test_redshift.py
+++ b/tests/test_redshift.py
@@ -742,3 +742,18 @@ def test_disable_s3_logging(self):
         result.pop('ResponseMetadata')
 
         self.assertFalse(result["LoggingEnabled"])
+
+
+class TestReservedNode(BaseTest):
+    def test_redshift_reserved_node_query(self):
+        session_factory = self.replay_flight_data("test_redshift_reserved_node_query")
+        p = self.load_policy(
+            {
+                "name": "redshift-reserved",
+                "resource": "aws.redshift-reserved"
+            },
+            session_factory=session_factory,
+        )
+        resources = p.run()
+        self.assertEqual(len(resources), 1)
+        self.assertEqual(resources[0]["ReservedNodeId"], "1ba8e2e3-bc01-4d65-b35d-a4a3e931547e")