Repository: cloud-custodian/cloud-custodian

Issue:
Tagging support for aws.backup-plan
resourcegrouptagging api supports AWS Backup


Discussion:

Resolving patch:
diff --git a/c7n/resources/backup.py b/c7n/resources/backup.py
--- a/c7n/resources/backup.py
+++ b/c7n/resources/backup.py
@@ -28,6 +28,7 @@ class resource_type(TypeInfo):
         id = 'BackupPlanName'
         name = 'BackupPlanId'
         arn = 'BackupPlanArn'
+        universal_taggable = object()
 
     def augment(self, resources):
         super(BackupPlan, self).augment(resources)
@@ -38,7 +39,7 @@ def augment(self, resources):
                 tags = client.list_tags(ResourceArn=r['BackupPlanArn']).get('Tags', {})
             except client.exceptions.ResourceNotFoundException:
                 continue
-            r['Tags'] = [{'Name': k, 'Value': v} for k, v in tags.items()]
+            r['Tags'] = [{'Key': k, 'Value': v} for k, v in tags.items()]
             results.append(r)
 
         return results


Test patch:
diff --git a/tests/data/placebo/test_backup_plan_tag_untag/backup.GetBackupPlan_1.json b/tests/data/placebo/test_backup_plan_tag_untag/backup.GetBackupPlan_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_backup_plan_tag_untag/backup.GetBackupPlan_1.json
@@ -0,0 +1,71 @@
+{
+    "status_code": 200,
+    "data": {
+        "ResponseMetadata": {
+            "RequestId": "1391171e-cc7c-4860-af58-5f41a148a7dc",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "content-type": "application/json",
+                "date": "Mon, 20 Jan 2020 20:10:25 GMT",
+                "x-amzn-requestid": "1391171e-cc7c-4860-af58-5f41a148a7dc",
+                "content-length": "1038",
+                "connection": "keep-alive"
+            },
+            "RetryAttempts": 0
+        },
+        "BackupPlan": {
+            "BackupPlanName": "test",
+            "Rules": [
+                {
+                    "RuleName": "dev",
+                    "TargetBackupVaultName": "Default",
+                    "ScheduleExpression": "cron(0 5 ? * * *)",
+                    "StartWindowMinutes": 480,
+                    "CompletionWindowMinutes": 10080,
+                    "Lifecycle": {
+                        "DeleteAfterDays": 21
+                    },
+                    "RecoveryPointTags": {
+                        "CostCenter": "Backup"
+                    },
+                    "RuleId": "1d5ef03b-d787-4a05-9e0a-04fdb2d4793c"
+                },
+                {
+                    "RuleName": "weekly-archive",
+                    "TargetBackupVaultName": "Default",
+                    "ScheduleExpression": "cron(0 5 ? * 7 *)",
+                    "StartWindowMinutes": 480,
+                    "CompletionWindowMinutes": 10080,
+                    "Lifecycle": {
+                        "MoveToColdStorageAfterDays": 90,
+                        "DeleteAfterDays": 180
+                    },
+                    "RuleId": "f9f067cb-c58d-4b8b-b38e-f2aa8e0a47b4"
+                }
+            ]
+        },
+        "BackupPlanId": "14c4daac-b199-45ce-8644-c541734ebd14",
+        "BackupPlanArn": "arn:aws:backup:us-east-1:644160558196:backup-plan:14c4daac-b199-45ce-8644-c541734ebd14",
+        "VersionId": "NTNlNjNkOGMtM2M4NS00NWMwLTk3YjMtNzk1NzNhMjRjMTZj",
+        "CreationDate": {
+            "__class__": "datetime",
+            "year": 2019,
+            "month": 1,
+            "day": 17,
+            "hour": 9,
+            "minute": 1,
+            "second": 58,
+            "microsecond": 667000
+        },
+        "LastExecutionDate": {
+            "__class__": "datetime",
+            "year": 2020,
+            "month": 1,
+            "day": 20,
+            "hour": 0,
+            "minute": 12,
+            "second": 44,
+            "microsecond": 186000
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_backup_plan_tag_untag/backup.ListBackupPlans_1.json b/tests/data/placebo/test_backup_plan_tag_untag/backup.ListBackupPlans_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_backup_plan_tag_untag/backup.ListBackupPlans_1.json
@@ -0,0 +1,45 @@
+{
+    "status_code": 200,
+    "data": {
+        "ResponseMetadata": {
+            "RequestId": "9dc4b2f4-9139-44bd-8501-c13f1e670cf3",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "content-type": "application/json",
+                "date": "Mon, 20 Jan 2020 20:10:25 GMT",
+                "x-amzn-requestid": "9dc4b2f4-9139-44bd-8501-c13f1e670cf3",
+                "content-length": "399",
+                "connection": "keep-alive"
+            },
+            "RetryAttempts": 0
+        },
+        "BackupPlansList": [
+            {
+                "BackupPlanArn": "arn:aws:backup:us-east-1:644160558196:backup-plan:14c4daac-b199-45ce-8644-c541734ebd14",
+                "BackupPlanId": "14c4daac-b199-45ce-8644-c541734ebd14",
+                "CreationDate": {
+                    "__class__": "datetime",
+                    "year": 2019,
+                    "month": 1,
+                    "day": 17,
+                    "hour": 9,
+                    "minute": 1,
+                    "second": 58,
+                    "microsecond": 667000
+                },
+                "VersionId": "NTNlNjNkOGMtM2M4NS00NWMwLTk3YjMtNzk1NzNhMjRjMTZj",
+                "BackupPlanName": "test",
+                "LastExecutionDate": {
+                    "__class__": "datetime",
+                    "year": 2020,
+                    "month": 1,
+                    "day": 20,
+                    "hour": 0,
+                    "minute": 12,
+                    "second": 44,
+                    "microsecond": 186000
+                }
+            }
+        ]
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_backup_plan_tag_untag/backup.ListTags_1.json b/tests/data/placebo/test_backup_plan_tag_untag/backup.ListTags_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_backup_plan_tag_untag/backup.ListTags_1.json
@@ -0,0 +1,20 @@
+{
+    "status_code": 200,
+    "data": {
+        "ResponseMetadata": {
+            "RequestId": "70fdb140-46b5-4a3e-985b-35ae66ecac3f",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "content-type": "application/json",
+                "date": "Mon, 20 Jan 2020 20:10:26 GMT",
+                "x-amzn-requestid": "70fdb140-46b5-4a3e-985b-35ae66ecac3f",
+                "content-length": "46",
+                "connection": "keep-alive"
+            },
+            "RetryAttempts": 0
+        },
+        "Tags": {
+            "target-tag": "yes"
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_backup_plan_tag_untag/backup.ListTags_2.json b/tests/data/placebo/test_backup_plan_tag_untag/backup.ListTags_2.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_backup_plan_tag_untag/backup.ListTags_2.json
@@ -0,0 +1,18 @@
+{
+    "status_code": 200,
+    "data": {
+        "ResponseMetadata": {
+            "RequestId": "8445dfaa-e0c0-4a3f-9ad9-ed0f118a607a",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "content-type": "application/json",
+                "date": "Mon, 20 Jan 2020 20:10:27 GMT",
+                "x-amzn-requestid": "8445dfaa-e0c0-4a3f-9ad9-ed0f118a607a",
+                "content-length": "28",
+                "connection": "keep-alive"
+            },
+            "RetryAttempts": 0
+        },
+        "Tags": {}
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_backup_plan_tag_untag/tagging.UntagResources_1.json b/tests/data/placebo/test_backup_plan_tag_untag/tagging.UntagResources_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_backup_plan_tag_untag/tagging.UntagResources_1.json
@@ -0,0 +1,17 @@
+{
+    "status_code": 200,
+    "data": {
+        "FailedResourcesMap": {},
+        "ResponseMetadata": {
+            "RequestId": "075bb746-2ec5-491e-91ab-243871ca46db",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "x-amzn-requestid": "075bb746-2ec5-491e-91ab-243871ca46db",
+                "content-type": "application/x-amz-json-1.1",
+                "content-length": "25",
+                "date": "Mon, 20 Jan 2020 20:10:26 GMT"
+            },
+            "RetryAttempts": 0
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/test_backup.py b/tests/test_backup.py
--- a/tests/test_backup.py
+++ b/tests/test_backup.py
@@ -28,7 +28,7 @@ def test_augment(self):
         plan = resources.pop()
         self.assertEqual(
             plan['Tags'],
-            [{'Name': 'App', 'Value': 'Backups'}])
+            [{'Key': 'App', 'Value': 'Backups'}])
         self.assertTrue('Rules' in plan)
 
         self.assertEqual(
@@ -36,3 +36,25 @@ def test_augment(self):
             [plan['BackupPlanArn']])
         resources = p.resource_manager.get_resources([plan['BackupPlanId']])
         self.assertEqual(len(resources), 1)
+
+
+class BackupPlanTest(BaseTest):
+
+    def test_backup_plan_tag_untag(self):
+        factory = self.replay_flight_data("test_backup_plan_tag_untag")
+        p = self.load_policy(
+            {
+                "name": "backup-plan-tag",
+                "resource": "backup-plan",
+                "filters": [{"tag:target-tag": "present"}],
+                "actions": [
+                    {"type": "remove-tag", "tags": ["target-tag"]},
+                ],
+            },
+            session_factory=factory,
+        )
+        resources = p.run()
+        self.assertEqual(len(resources), 1)
+        client = factory().client("backup")
+        tag = client.list_tags(ResourceArn=resources[0]['BackupPlanArn'])
+        self.assertEqual(len(tag.get('Tags')), 0)