Repository: cloud-custodian/cloud-custodian

Issue:
Request for delete action for resource Cloud Watch Alarms
Hi,

Can you guys put in a "Delete" action for the resource "alarm"

Thank you


Discussion:
Just to understand the use case more, What criteria would you be using to filter alarms to delete
StateValue and Age for now, was able to filter successfully using these two
Alarms have a hard limit of 5000/account as of now. We hit that and had to manually delete over 4000 alarms which were not being used at all.
how do you determine not used?
@kapilt :Once an instance gets deleted, its cloud watch alarm stays behind(doesn't get deleted). We rehydrate our instances every 100 days, after the instance gets deleted it goes into  "StateValue: INSUFFICIENT_DATA" and we can filter the age based on the updated timestamp of the "AlarmConfigurationUpdatedTimestamp"
+1, we are also in need for this action. 

Thanks. 

Resolving patch:
diff --git a/c7n/filters/core.py b/c7n/filters/core.py
--- a/c7n/filters/core.py
+++ b/c7n/filters/core.py
@@ -349,6 +349,7 @@ def process_value_type(self, sentinel, value):
                     value = parse(value)
                 except (AttributeError, TypeError):
                     value = 0
+
             # Reverse the age comparison, we want to compare the value being
             # greater than the sentinel typically. Else the syntax for age
             # comparisons is intuitively wrong.
diff --git a/c7n/resources/cw.py b/c7n/resources/cw.py
--- a/c7n/resources/cw.py
+++ b/c7n/resources/cw.py
@@ -17,7 +17,7 @@
 from c7n.filters import Filter
 from c7n.query import QueryResourceManager
 from c7n.manager import resources
-from c7n.utils import type_schema, local_session
+from c7n.utils import type_schema, local_session, chunks, get_retry
 
 
 @resources.register('alarm')
@@ -34,6 +34,43 @@ class resource_type(object):
         date = 'AlarmConfigurationUpdatedTimestamp'
         dimension = None
 
+    retry = staticmethod(get_retry(('Throttled',)))
+
+
+@Alarm.action_registry.register('delete')
+class AlarmDelete(BaseAction):
+    """Delete a cloudwatch alarm.
+
+    :example:
+
+        .. code-block: yaml
+
+            policies:
+              - name: cloudwatch-delete-stale-alarms
+                resource: alarm
+                filters:
+                  - type: value
+                    value_type: age
+                    key: StateUpdatedTimestamp
+                    value: 30
+                    op: ge
+                  - StateValue: INSUFFICIENT_DATA
+                actions:
+                  - delete
+    """
+
+    schema = type_schema('delete')
+    permissions = ('cloudwatch:DeleteAlarms',)
+
+    def process(self, resources):
+        client = local_session(
+            self.manager.session_factory).client('cloudwatch')
+
+        for resource_set in chunks(resources, size=100):
+            self.manager.retry(
+                client.delete_alarms,
+                AlarmNames=[r['AlarmName'] for r in resource_set])
+
 
 @resources.register('event-rule')
 class EventRule(QueryResourceManager):


Test patch:
diff --git a/tests/data/placebo/test_alarm_delete/monitoring.DeleteAlarms_1.json b/tests/data/placebo/test_alarm_delete/monitoring.DeleteAlarms_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_alarm_delete/monitoring.DeleteAlarms_1.json
@@ -0,0 +1,16 @@
+{
+    "status_code": 200, 
+    "data": {
+        "ResponseMetadata": {
+            "RetryAttempts": 0, 
+            "HTTPStatusCode": 200, 
+            "RequestId": "61c3dd99-f6d2-11e6-a068-1b16d84e0be4", 
+            "HTTPHeaders": {
+                "x-amzn-requestid": "61c3dd99-f6d2-11e6-a068-1b16d84e0be4", 
+                "date": "Sun, 19 Feb 2017 18:36:52 GMT", 
+                "content-length": "210", 
+                "content-type": "text/xml"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_alarm_delete/monitoring.DescribeAlarms_1.json b/tests/data/placebo/test_alarm_delete/monitoring.DescribeAlarms_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_alarm_delete/monitoring.DescribeAlarms_1.json
@@ -0,0 +1,56 @@
+{
+    "status_code": 200, 
+    "data": {
+        "ResponseMetadata": {
+            "RetryAttempts": 0, 
+            "HTTPStatusCode": 200, 
+            "RequestId": "61a50b15-f6d2-11e6-aa71-e7fe982c91c5", 
+            "HTTPHeaders": {
+                "x-amzn-requestid": "61a50b15-f6d2-11e6-aa71-e7fe982c91c5", 
+                "date": "Sun, 19 Feb 2017 18:36:51 GMT", 
+                "content-length": "1252", 
+                "content-type": "text/xml"
+            }
+        }, 
+        "MetricAlarms": [
+            {
+                "EvaluationPeriods": 5, 
+                "AlarmArn": "arn:aws:cloudwatch:us-east-1:644160558196:alarm:c7n-test-alarm-delete", 
+                "StateUpdatedTimestamp": {
+                    "hour": 18, 
+                    "__class__": "datetime", 
+                    "month": 2, 
+                    "second": 51, 
+                    "microsecond": 810000, 
+                    "year": 2017, 
+                    "day": 19, 
+                    "minute": 36
+                }, 
+                "AlarmConfigurationUpdatedTimestamp": {
+                    "hour": 18, 
+                    "__class__": "datetime", 
+                    "month": 2, 
+                    "second": 51, 
+                    "microsecond": 810000, 
+                    "year": 2017, 
+                    "day": 19, 
+                    "minute": 36
+                }, 
+                "ComparisonOperator": "GreaterThanThreshold", 
+                "AlarmActions": [], 
+                "Namespace": "AWS/EC2", 
+                "Period": 3600, 
+                "StateValue": "INSUFFICIENT_DATA", 
+                "Threshold": 10.0, 
+                "AlarmName": "c7n-test-alarm-delete", 
+                "Dimensions": [], 
+                "Statistic": "Average", 
+                "StateReason": "Unchecked: Initial alarm creation", 
+                "InsufficientDataActions": [], 
+                "OKActions": [], 
+                "ActionsEnabled": true, 
+                "MetricName": "CPUUtilization"
+            }
+        ]
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_alarm_delete/monitoring.DescribeAlarms_2.json b/tests/data/placebo/test_alarm_delete/monitoring.DescribeAlarms_2.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_alarm_delete/monitoring.DescribeAlarms_2.json
@@ -0,0 +1,17 @@
+{
+    "status_code": 200, 
+    "data": {
+        "ResponseMetadata": {
+            "RetryAttempts": 0, 
+            "HTTPStatusCode": 200, 
+            "RequestId": "61d08791-f6d2-11e6-bcf7-97c3012933f3", 
+            "HTTPHeaders": {
+                "x-amzn-requestid": "61d08791-f6d2-11e6-bcf7-97c3012933f3", 
+                "date": "Sun, 19 Feb 2017 18:36:51 GMT", 
+                "content-length": "285", 
+                "content-type": "text/xml"
+            }
+        }, 
+        "MetricAlarms": []
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_alarm_delete/monitoring.PutMetricAlarm_1.json b/tests/data/placebo/test_alarm_delete/monitoring.PutMetricAlarm_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_alarm_delete/monitoring.PutMetricAlarm_1.json
@@ -0,0 +1,16 @@
+{
+    "status_code": 200, 
+    "data": {
+        "ResponseMetadata": {
+            "RetryAttempts": 0, 
+            "HTTPStatusCode": 200, 
+            "RequestId": "6156284d-f6d2-11e6-90c6-530dbf0e4192", 
+            "HTTPHeaders": {
+                "x-amzn-requestid": "6156284d-f6d2-11e6-90c6-530dbf0e4192", 
+                "date": "Sun, 19 Feb 2017 18:36:51 GMT", 
+                "content-length": "214", 
+                "content-type": "text/xml"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/test_cwa.py b/tests/test_cwa.py
new file mode 100644
--- /dev/null
+++ b/tests/test_cwa.py
@@ -0,0 +1,46 @@
+# Copyright 2016 Capital One Services, LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from common import BaseTest
+
+
+class AlarmTest(BaseTest):
+
+    def test_delete(self):
+        alarm_name = 'c7n-test-alarm-delete'
+        factory = self.replay_flight_data('test_alarm_delete')
+        client = factory().client('cloudwatch')
+        client.put_metric_alarm(
+            AlarmName=alarm_name,
+            MetricName='CPUUtilization',
+            Namespace='AWS/EC2',
+            Statistic='Average',
+            Period=3600,
+            EvaluationPeriods=5,
+            Threshold=10,
+            ComparisonOperator='GreaterThanThreshold')
+
+        p = self.load_policy(
+            {'name': 'delete-alarm',
+             'resource': 'alarm',
+             'filters': [{'AlarmName': alarm_name}],
+             'actions': ['delete']
+             },
+            session_factory=factory)
+
+        resources = p.run()
+        self.assertEqual(len(resources), 1)
+        self.assertEqual(
+            client.describe_alarms(
+                AlarmNames=[alarm_name])['MetricAlarms'], [])