Repository: cloud-custodian/cloud-custodian

Issue:
Feature Request - SNS Topic Delete
Hi,

I have just created this rule:

```
policies:
  - name: untagged-sns-rule
    resource: sns
    mode:
     type: cloudtrail
     role: arn:aws:iam::{account_id}:role/company/cloudteam/cloudcustodian-sns-role
     events:
      - event: CreateQueue
        ids: requestParameters.queueName
        source: sqs.amazonaws.com
     tags:
      Name: Untagged_SNS_Rule
      Role: CloudTeam
      Contact: CloudTeam
      Project: Custodian
      Owner: CloudTeam
      Team: CloudTeam
      Client: Company
      Environment: Development
    filters:
     - "tag:Name": empty
     - "tag:Role": empty
     - "tag:Contact": empty
     - "tag:Project": empty
     - "tag:Team": empty
     - "tag:Owner": empty
     - "tag:Client": empty
     - "tag:Environment": empty
    actions:
     - delete
```

I have then come to find that SNS topics can't be deleted by Cloud Custodian Rules.

Please can this be added.




Discussion:

Resolving patch:
diff --git a/c7n/resources/sns.py b/c7n/resources/sns.py
--- a/c7n/resources/sns.py
+++ b/c7n/resources/sns.py
@@ -325,3 +325,34 @@ def process(self, resources):
                 AttributeValue=key
             )
         return resources
+
+
+@SNS.action_registry.register('delete')
+class DeleteTopic(BaseAction):
+    """
+    Deletes a SNS Topic
+
+    :example:
+
+    .. code-block:: yaml
+
+      policies:
+        - name: delete-bad-topic
+          resource: aws.sns
+          filters:
+            - TopicArn: arn:aws:sns:us-east-2:123456789012:BadTopic
+          actions:
+            - type: delete
+    """
+
+    schema = type_schema('delete')
+
+    permissions = ('sns:DeleteTopic',)
+
+    def process(self, resources):
+        client = local_session(self.manager.session_factory).client('sns')
+        for r in resources:
+            try:
+                client.delete_topic(TopicArn=r['TopicArn'])
+            except client.exceptions.NotFoundException:
+                continue


Test patch:
diff --git a/tests/data/placebo/test_sns_delete_topic/sns.DeleteTopic_1.json b/tests/data/placebo/test_sns_delete_topic/sns.DeleteTopic_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_sns_delete_topic/sns.DeleteTopic_1.json
@@ -0,0 +1,16 @@
+{
+    "status_code": 200,
+    "data": {
+        "ResponseMetadata": {
+            "RequestId": "2253ddd5-73f2-53e4-a085-88b77220630b",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "x-amzn-requestid": "2253ddd5-73f2-53e4-a085-88b77220630b",
+                "content-type": "text/xml",
+                "content-length": "201",
+                "date": "Fri, 24 May 2019 14:28:34 GMT"
+            },
+            "RetryAttempts": 0
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_sns_delete_topic/sns.GetTopicAttributes_1.json b/tests/data/placebo/test_sns_delete_topic/sns.GetTopicAttributes_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_sns_delete_topic/sns.GetTopicAttributes_1.json
@@ -0,0 +1,27 @@
+{
+    "status_code": 200,
+    "data": {
+        "Attributes": {
+            "Policy": "{\"Version\":\"2008-10-17\",\"Id\":\"__default_policy_ID\",\"Statement\":[{\"Sid\":\"__default_statement_ID\",\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"*\"},\"Action\":[\"SNS:GetTopicAttributes\",\"SNS:SetTopicAttributes\",\"SNS:AddPermission\",\"SNS:RemovePermission\",\"SNS:DeleteTopic\",\"SNS:Subscribe\",\"SNS:ListSubscriptionsByTopic\",\"SNS:Publish\",\"SNS:Receive\"],\"Resource\":\"arn:aws:sns:us-west-1:644160558196:test\",\"Condition\":{\"StringEquals\":{\"AWS:SourceOwner\":\"644160558196\"}}}]}",
+            "Owner": "644160558196",
+            "SubscriptionsPending": "0",
+            "TopicArn": "arn:aws:sns:us-west-1:644160558196:test",
+            "EffectiveDeliveryPolicy": "{\"http\":{\"defaultHealthyRetryPolicy\":{\"minDelayTarget\":20,\"maxDelayTarget\":20,\"numRetries\":3,\"numMaxDelayRetries\":0,\"numNoDelayRetries\":0,\"numMinDelayRetries\":0,\"backoffFunction\":\"linear\"},\"disableSubscriptionOverrides\":false}}",
+            "SubscriptionsConfirmed": "0",
+            "DisplayName": "",
+            "SubscriptionsDeleted": "0"
+        },
+        "ResponseMetadata": {
+            "RequestId": "648cc6e1-d144-56b7-aa1c-9383d0953023",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "x-amzn-requestid": "648cc6e1-d144-56b7-aa1c-9383d0953023",
+                "content-type": "text/xml",
+                "content-length": "2125",
+                "vary": "Accept-Encoding",
+                "date": "Fri, 24 May 2019 14:28:33 GMT"
+            },
+            "RetryAttempts": 0
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_sns_delete_topic/sns.ListTopics_1.json b/tests/data/placebo/test_sns_delete_topic/sns.ListTopics_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_sns_delete_topic/sns.ListTopics_1.json
@@ -0,0 +1,21 @@
+{
+    "status_code": 200,
+    "data": {
+        "Topics": [
+            {
+                "TopicArn": "arn:aws:sns:us-west-1:644160558196:test"
+            }
+        ],
+        "ResponseMetadata": {
+            "RequestId": "134516c9-a5f9-50ef-94d5-43aba428967b",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "x-amzn-requestid": "134516c9-a5f9-50ef-94d5-43aba428967b",
+                "content-type": "text/xml",
+                "content-length": "369",
+                "date": "Fri, 24 May 2019 14:28:32 GMT"
+            },
+            "RetryAttempts": 0
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_sns_delete_topic/sns.ListTopics_2.json b/tests/data/placebo/test_sns_delete_topic/sns.ListTopics_2.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_sns_delete_topic/sns.ListTopics_2.json
@@ -0,0 +1,17 @@
+{
+    "status_code": 200,
+    "data": {
+        "Topics": [],
+        "ResponseMetadata": {
+            "RequestId": "ef2a41e9-b9b3-5d5e-878f-148bb8606fb0",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "x-amzn-requestid": "ef2a41e9-b9b3-5d5e-878f-148bb8606fb0",
+                "content-type": "text/xml",
+                "content-length": "256",
+                "date": "Fri, 24 May 2019 14:28:34 GMT"
+            },
+            "RetryAttempts": 0
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/test_sns.py b/tests/test_sns.py
--- a/tests/test_sns.py
+++ b/tests/test_sns.py
@@ -16,6 +16,7 @@
 import json
 
 from .common import BaseTest, functional
+from c7n.utils import yaml_load
 
 
 class TestSNS(BaseTest):
@@ -593,3 +594,20 @@ def test_sns_set_encryption_custom_key(self):
         self.assertEqual(len(resources), 1)
         attributes = sns.get_topic_attributes(TopicArn=topic)['Attributes']
         self.assertEqual(attributes.get('KmsMasterKeyId'), key_alias)
+
+    def test_sns_delete(self):
+        session_factory = self.replay_flight_data('test_sns_delete_topic')
+        policy = """
+        name: delete-sns
+        resource: aws.sns
+        filters:
+          - TopicArn: arn:aws:sns:us-west-1:644160558196:test
+        actions:
+          - type: delete
+        """
+        p = self.load_policy(yaml_load(policy), session_factory=session_factory)
+        resources = p.run()
+        self.assertEqual(len(resources), 1)
+        client = session_factory().client('sns')
+        resources = client.list_topics()['Topics']
+        self.assertEqual(len(resources), 0)