Repository: cloud-custodian/cloud-custodian

Issue:
Add 'mark-for-op' and 'marked-for-op' to IAM role
We would like to mark / tag IAM roles for removal after a threshold period, using a policy similar to:

```yaml
- name: iam-role-unused-mark
  resource: aws.iam-role
  filters:
    - "tag:maid_status": absent
    - type: used
      state: false
    - type: usage
      match-operator: all
      LastAuthenticated: null
  actions:
    - type: mark-for-op
      tag: maid_status
      op: delete
```

This is possible by registering the respective actions to the IAM role action and filter registries: https://github.com/FundingCircle/cloud-custodian/blob/27b32aa44b75077deb54e1857e39dffbdf26904e/c7n/resources/iam.py#L291-L292


Discussion:

Resolving patch:
diff --git a/c7n/resources/iam.py b/c7n/resources/iam.py
--- a/c7n/resources/iam.py
+++ b/c7n/resources/iam.py
@@ -283,6 +283,10 @@ def process_resource_set(self, client, users, tags):
 User.filter_registry.register('marked-for-op', TagActionFilter)
 
 
+Role.action_registry.register('mark-for-op', TagDelayedAction)
+Role.filter_registry.register('marked-for-op', TagActionFilter)
+
+
 @User.action_registry.register('set-groups')
 class SetGroups(BaseAction):
     """Set a specific IAM user as added/removed from a group


Test patch:
diff --git a/tests/data/placebo/test_iam_role_tags/iam.GetRole_1.json b/tests/data/placebo/test_iam_role_tags/iam.GetRole_1.json
--- a/tests/data/placebo/test_iam_role_tags/iam.GetRole_1.json
+++ b/tests/data/placebo/test_iam_role_tags/iam.GetRole_1.json
@@ -17,6 +17,10 @@
                 "minute": 41
             }, 
             "Tags": [
+                {
+                    "Key": "maid_status",
+                    "Value": "Resource does not meet policy: delete@2019/01/25"
+                },
                 {
                     "Value": "One", 
                     "Key": "Application"
@@ -46,4 +50,4 @@
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/tests/data/placebo/test_iam_role_tags/iam.TagUser_2.json b/tests/data/placebo/test_iam_role_tags/iam.TagUser_2.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_iam_role_tags/iam.TagUser_2.json
@@ -0,0 +1,16 @@
+{
+    "status_code": 200,
+    "data": {
+        "ResponseMetadata": {
+            "RequestId": "a5c516b3-1f5c-11e9-a82f-d343ab04b38b",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "x-amzn-requestid": "a5c516b3-1f5c-11e9-a82f-d343ab04b38b",
+                "content-type": "text/xml",
+                "content-length": "194",
+                "date": "Wed, 23 Jan 2019 22:17:18 GMT"
+            },
+            "RetryAttempts": 0
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/test_iam.py b/tests/test_iam.py
--- a/tests/test_iam.py
+++ b/tests/test_iam.py
@@ -492,7 +492,10 @@ def test_iam_role_actions(self):
                 {'type': 'tag',
                  'tags': {'Env': 'Dev'}},
                 {'type': 'remove-tag',
-                 'tags': ['Application']}
+                 'tags': ['Application']},
+                {'type': 'mark-for-op',
+                 'op': 'delete',
+                 'days': 2}
             ]
         },
             session_factory=factory)
@@ -512,6 +515,9 @@ def test_iam_role_actions(self):
         self.assertNotIn(
             {'Application'},
             {t['Key'] for t in role['Tags']})
+        self.assertEqual(
+            {'maid_status': 'Resource does not meet policy: delete@2019/01/25'},
+            {t['Key']: t['Value'] for t in resources[0]['Tags'] if t['Key'] == 'maid_status'})
 
     def test_iam_role_set_boundary(self):
         factory = self.replay_flight_data('test_iam_role_set_boundary')