Repository: cloud-custodian/cloud-custodian

Issue:
aws - dynamodb resource group tagging support
This should reduce api call volume on retrieving tags and mutating.


Discussion:

Resolving patch:
diff --git a/c7n/actions/autotag.py b/c7n/actions/autotag.py
--- a/c7n/actions/autotag.py
+++ b/c7n/actions/autotag.py
@@ -86,6 +86,9 @@ def validate(self):
         if self.manager.action_registry.get('tag') is None:
             raise PolicyValidationError(
                 "Resource does not support tagging %s" % (self.manager.data,))
+        if 'tag' not in self.data:
+            raise PolicyValidationError(
+                "auto-tag action requires 'tag'")
         return self
 
     def process(self, resources, event):
diff --git a/c7n/resources/dynamodb.py b/c7n/resources/dynamodb.py
--- a/c7n/resources/dynamodb.py
+++ b/c7n/resources/dynamodb.py
@@ -1,4 +1,4 @@
-# Copyright 2016-2017 Capital One Services, LLC
+# Copyright 2016-2019 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.
@@ -23,7 +23,9 @@
 from c7n.filters.kms import KmsRelatedFilter
 from c7n import query
 from c7n.manager import resources
-from c7n.tags import TagDelayedAction, RemoveTag, TagActionFilter, Tag
+from c7n.tags import (
+    TagDelayedAction, RemoveTag, TagActionFilter, Tag, universal_augment,
+    register_universal_tags)
 from c7n.utils import (
     local_session, get_retry, chunks, type_schema, snapshot_identifier)
 from c7n.filters.vpc import SecurityGroupFilter, SubnetFilter
@@ -60,39 +62,15 @@ def get_source(self, source_type):
         raise ValueError('invalid source %s' % source_type)
 
 
-class DescribeTable(query.DescribeSource):
-
-    def augment(self, tables):
-        resources = super(DescribeTable, self).augment(tables)
-        return list(filter(None, _dynamodb_table_tags(
-            self.manager.get_model(),
-            resources,
-            self.manager.session_factory,
-            self.manager.executor_factory,
-            self.manager.retry,
-            self.manager.log)))
-
+register_universal_tags(Table.filter_registry, Table.action_registry, False)
 
-def _dynamodb_table_tags(
-        model, tables, session_factory, executor_factory, retry, log):
-    """ Augment DynamoDB tables with their respective tags
-    """
 
-    def process_tags(table):
-        client = local_session(session_factory).client('dynamodb')
-        arn = table['TableArn']
-        try:
-            tag_list = retry(
-                client.list_tags_of_resource,
-                ResourceArn=arn)['Tags']
-        except ClientError as e:
-            log.warning("Exception getting DynamoDB tags  \n %s", e)
-            return None
-        table['Tags'] = tag_list or []
-        return table
+class DescribeTable(query.DescribeSource):
 
-    with executor_factory(max_workers=2) as w:
-        return list(w.map(process_tags, tables))
+    def augment(self, resources):
+        return universal_augment(
+            self.manager,
+            super(DescribeTable, self).augment(resources))
 
 
 class StatusFilter(object):
@@ -139,98 +117,6 @@ class KmsFilter(KmsRelatedFilter):
     RelatedIdsExpression = 'SSEDescription.KMSMasterKeyArn'
 
 
-@Table.action_registry.register('mark-for-op')
-class TagDelayedAction(TagDelayedAction):
-    """Action to specify an action to occur at a later date
-
-    :example:
-
-    .. code-block:: yaml
-
-            policies:
-              - name: dynamo-mark-tag-compliance
-                resource: dynamodb-table
-                filters:
-                  - "tag:custodian_cleanup": absent
-                  - "tag:OwnerName": absent
-                actions:
-                  - type: mark-for-op
-                    tag: custodian_cleanup
-                    msg: "Cluster does not have valid OwnerName tag: {op}@{action_date}"
-                    op: delete
-                    days: 7
-    """
-    permission = ('dynamodb:TagResource',)
-    batch_size = 1
-
-    def process_resource_set(self, tables, tags):
-        client = local_session(self.manager.session_factory).client(
-            'dynamodb')
-        for t in tables:
-            arn = t['TableArn']
-            client.tag_resource(ResourceArn=arn, Tags=tags)
-
-
-@Table.action_registry.register('tag')
-class TagTable(Tag):
-    """Action to create tag(s) on a resource
-
-    :example:
-
-    .. code-block:: yaml
-
-            policies:
-              - name: dynamodb-tag-table
-                resource: dynamodb-table
-                filters:
-                  - "tag:target-tag": absent
-                actions:
-                  - type: tag
-                    key: target-tag
-                    value: target-tag-value
-    """
-
-    permissions = ('dynamodb:TagResource',)
-    batch_size = 1
-
-    def process_resource_set(self, tables, tags):
-        client = local_session(self.manager.session_factory).client('dynamodb')
-        for t in tables:
-            arn = t['TableArn']
-            client.tag_resource(ResourceArn=arn, Tags=tags)
-
-
-@Table.action_registry.register('remove-tag')
-class UntagTable(RemoveTag):
-    """Action to remove tag(s) on a resource
-
-    :example:
-
-    .. code-block:: yaml
-
-            policies:
-              - name: dynamodb-remove-tag
-                resource: dynamodb-table
-                filters:
-                  - "tag:OutdatedTag": present
-                actions:
-                  - type: remove-tag
-                    tags: ["OutdatedTag"]
-    """
-
-    concurrency = 2
-    batch_size = 5
-    permissions = ('dynamodb:UntagResource',)
-
-    def process_resource_set(self, tables, tag_keys):
-        client = local_session(
-            self.manager.session_factory).client('dynamodb')
-        for t in tables:
-            arn = t['TableArn']
-            client.untag_resource(
-                ResourceArn=arn, TagKeys=tag_keys)
-
-
 @Table.action_registry.register('delete')
 class DeleteTable(BaseAction, StatusFilter):
     """Action to delete dynamodb tables
diff --git a/c7n/tags.py b/c7n/tags.py
--- a/c7n/tags.py
+++ b/c7n/tags.py
@@ -59,9 +59,9 @@ def register_ec2_tags(filters, actions):
 
 def register_universal_tags(filters, actions, compatibility=True):
     filters.register('marked-for-op', TagActionFilter)
-    filters.register('tag-count', TagCountFilter)
 
     if compatibility:
+        filters.register('tag-count', TagCountFilter)
         actions.register('mark', UniversalTag)
 
     actions.register('tag', UniversalTag)
@@ -71,6 +71,7 @@ def register_universal_tags(filters, actions, compatibility=True):
     if compatibility:
         actions.register('unmark', UniversalUntag)
         actions.register('untag', UniversalUntag)
+
     actions.register('remove-tag', UniversalUntag)
 
 


Test patch:
diff --git a/tests/data/placebo/test_dynamodb_create_backup/dynamodb.ListTagsOfResource_1.json b/tests/data/placebo/test_dynamodb_create_backup/dynamodb.ListTagsOfResource_1.json
deleted file mode 100644
--- a/tests/data/placebo/test_dynamodb_create_backup/dynamodb.ListTagsOfResource_1.json
+++ /dev/null
@@ -1,20 +0,0 @@
-{
-    "status_code": 200, 
-    "data": {
-        "ResponseMetadata": {
-            "RetryAttempts": 0, 
-            "HTTPStatusCode": 200, 
-            "RequestId": "T3TKSDISEPQGSHL2HDIH7B8MJRVV4KQNSO5AEMVJF66Q9ASUAAJG", 
-            "HTTPHeaders": {
-                "x-amzn-requestid": "T3TKSDISEPQGSHL2HDIH7B8MJRVV4KQNSO5AEMVJF66Q9ASUAAJG", 
-                "content-length": "11", 
-                "server": "Server", 
-                "connection": "keep-alive", 
-                "x-amz-crc32": "1756070277", 
-                "date": "Wed, 17 Jan 2018 00:39:01 GMT", 
-                "content-type": "application/x-amz-json-1.0"
-            }
-        }, 
-        "Tags": []
-    }
-}
\ No newline at end of file
diff --git a/tests/data/placebo/test_dynamodb_create_backup/tagging.GetResources_1.json b/tests/data/placebo/test_dynamodb_create_backup/tagging.GetResources_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_dynamodb_create_backup/tagging.GetResources_1.json
@@ -0,0 +1,18 @@
+{
+    "status_code": 200,
+    "data": {
+        "PaginationToken": "",
+        "ResourceTagMappingList": [],
+        "ResponseMetadata": {
+            "RequestId": "15b6d484-217e-11e9-872f-c56b57c1cf00",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "x-amzn-requestid": "15b6d484-217e-11e9-872f-c56b57c1cf00",
+                "content-type": "application/x-amz-json-1.1",
+                "content-length": "230",
+                "date": "Sat, 26 Jan 2019 15:21:42 GMT"
+            },
+            "RetryAttempts": 0
+        }
+    }
+}
diff --git a/tests/data/placebo/test_dynamodb_create_prefixed_backup/dynamodb.ListTagsOfResource_1.json b/tests/data/placebo/test_dynamodb_create_prefixed_backup/dynamodb.ListTagsOfResource_1.json
deleted file mode 100644
--- a/tests/data/placebo/test_dynamodb_create_prefixed_backup/dynamodb.ListTagsOfResource_1.json
+++ /dev/null
@@ -1,20 +0,0 @@
-{
-    "status_code": 200, 
-    "data": {
-        "ResponseMetadata": {
-            "RetryAttempts": 0, 
-            "HTTPStatusCode": 200, 
-            "RequestId": "7M93P8T2UABUJKUFA08T5JDV4FVV4KQNSO5AEMVJF66Q9ASUAAJG", 
-            "HTTPHeaders": {
-                "x-amzn-requestid": "7M93P8T2UABUJKUFA08T5JDV4FVV4KQNSO5AEMVJF66Q9ASUAAJG", 
-                "content-length": "11", 
-                "server": "Server", 
-                "connection": "keep-alive", 
-                "x-amz-crc32": "1756070277", 
-                "date": "Mon, 22 Jan 2018 18:42:47 GMT", 
-                "content-type": "application/x-amz-json-1.0"
-            }
-        }, 
-        "Tags": []
-    }
-}
\ No newline at end of file
diff --git a/tests/data/placebo/test_dynamodb_create_prefixed_backup/tagging.GetResources_1.json b/tests/data/placebo/test_dynamodb_create_prefixed_backup/tagging.GetResources_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_dynamodb_create_prefixed_backup/tagging.GetResources_1.json
@@ -0,0 +1,18 @@
+{
+    "status_code": 200,
+    "data": {
+        "PaginationToken": "",
+        "ResourceTagMappingList": [],
+        "ResponseMetadata": {
+            "RequestId": "15b6d484-217e-11e9-872f-c56b57c1cf00",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "x-amzn-requestid": "15b6d484-217e-11e9-872f-c56b57c1cf00",
+                "content-type": "application/x-amz-json-1.1",
+                "content-length": "230",
+                "date": "Sat, 26 Jan 2019 15:21:42 GMT"
+            },
+            "RetryAttempts": 0
+        }
+    }
+}
diff --git a/tests/data/placebo/test_dynamodb_delete_table/dynamodb.ListTagsOfResource_1.json b/tests/data/placebo/test_dynamodb_delete_table/dynamodb.ListTagsOfResource_1.json
deleted file mode 100644
--- a/tests/data/placebo/test_dynamodb_delete_table/dynamodb.ListTagsOfResource_1.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
-    "status_code": 200, 
-    "data": {
-        "ResponseMetadata": {
-            "RetryAttempts": 0, 
-            "HTTPStatusCode": 200, 
-            "RequestId": "57BIJH9PBVC3ERAISL5M6I2DBNVV4KQNSO5AEMVJF66Q9ASUAAJG", 
-            "HTTPHeaders": {
-                "x-amzn-requestid": "57BIJH9PBVC3ERAISL5M6I2DBNVV4KQNSO5AEMVJF66Q9ASUAAJG", 
-                "date": "Tue, 31 Jan 2017 19:34:51 GMT", 
-                "content-length": "160", 
-                "content-type": "application/x-amz-json-1.0", 
-                "x-amz-crc32": "2210834338"
-            }
-        }, 
-        "Tags": [
-            {
-                "Value": "test_value", 
-                "Key": "test_key"
-            }
-        ]
-    }
-}
\ No newline at end of file
diff --git a/tests/data/placebo/test_dynamodb_delete_table/tagging.GetResources_1.json b/tests/data/placebo/test_dynamodb_delete_table/tagging.GetResources_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_dynamodb_delete_table/tagging.GetResources_1.json
@@ -0,0 +1,18 @@
+{
+    "status_code": 200,
+    "data": {
+        "PaginationToken": "",
+        "ResourceTagMappingList": [],
+        "ResponseMetadata": {
+            "RequestId": "15b6d484-217e-11e9-872f-c56b57c1cf00",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "x-amzn-requestid": "15b6d484-217e-11e9-872f-c56b57c1cf00",
+                "content-type": "application/x-amz-json-1.1",
+                "content-length": "230",
+                "date": "Sat, 26 Jan 2019 15:21:42 GMT"
+            },
+            "RetryAttempts": 0
+        }
+    }
+}
diff --git a/tests/data/placebo/test_dynamodb_enable_stream/dynamodb.ListTagsOfResource_1.json b/tests/data/placebo/test_dynamodb_enable_stream/dynamodb.ListTagsOfResource_1.json
deleted file mode 100644
--- a/tests/data/placebo/test_dynamodb_enable_stream/dynamodb.ListTagsOfResource_1.json
+++ /dev/null
@@ -1,20 +0,0 @@
-{
-    "status_code": 200, 
-    "data": {
-        "ResponseMetadata": {
-            "RetryAttempts": 0, 
-            "HTTPStatusCode": 200, 
-            "RequestId": "R1UR4NONSAIB7TQNFA00JINN0VVV4KQNSO5AEMVJF66Q9ASUAAJG", 
-            "HTTPHeaders": {
-                "x-amzn-requestid": "R1UR4NONSAIB7TQNFA00JINN0VVV4KQNSO5AEMVJF66Q9ASUAAJG", 
-                "content-length": "11", 
-                "server": "Server", 
-                "connection": "keep-alive", 
-                "x-amz-crc32": "1756070277", 
-                "date": "Thu, 22 Feb 2018 01:58:39 GMT", 
-                "content-type": "application/x-amz-json-1.0"
-            }
-        }, 
-        "Tags": []
-    }
-}
\ No newline at end of file
diff --git a/tests/data/placebo/test_dynamodb_enable_stream/tagging.GetResources_1.json b/tests/data/placebo/test_dynamodb_enable_stream/tagging.GetResources_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_dynamodb_enable_stream/tagging.GetResources_1.json
@@ -0,0 +1,18 @@
+{
+    "status_code": 200,
+    "data": {
+        "PaginationToken": "",
+        "ResourceTagMappingList": [],
+        "ResponseMetadata": {
+            "RequestId": "15b6d484-217e-11e9-872f-c56b57c1cf00",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "x-amzn-requestid": "15b6d484-217e-11e9-872f-c56b57c1cf00",
+                "content-type": "application/x-amz-json-1.1",
+                "content-length": "230",
+                "date": "Sat, 26 Jan 2019 15:21:42 GMT"
+            },
+            "RetryAttempts": 0
+        }
+    }
+}
diff --git a/tests/data/placebo/test_dynamodb_invoke_action/dynamodb.ListTagsOfResource_1.json b/tests/data/placebo/test_dynamodb_invoke_action/dynamodb.ListTagsOfResource_1.json
deleted file mode 100644
--- a/tests/data/placebo/test_dynamodb_invoke_action/dynamodb.ListTagsOfResource_1.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
-    "status_code": 200, 
-    "data": {
-        "ResponseMetadata": {
-            "RetryAttempts": 0, 
-            "HTTPStatusCode": 200, 
-            "RequestId": "57BIJH9PBVC3ERAISL5M6I2DBNVV4KQNSO5AEMVJF66Q9ASUAAJG", 
-            "HTTPHeaders": {
-                "x-amzn-requestid": "57BIJH9PBVC3ERAISL5M6I2DBNVV4KQNSO5AEMVJF66Q9ASUAAJG", 
-                "date": "Tue, 31 Jan 2017 19:34:51 GMT", 
-                "content-length": "160", 
-                "content-type": "application/x-amz-json-1.0", 
-                "x-amz-crc32": "2210834338"
-            }
-        }, 
-        "Tags": [
-            {
-                "Value": "test_value", 
-                "Key": "test_key"
-            }
-        ]
-    }
-}
\ No newline at end of file
diff --git a/tests/data/placebo/test_dynamodb_invoke_action/tagging.GetResources_1.json b/tests/data/placebo/test_dynamodb_invoke_action/tagging.GetResources_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_dynamodb_invoke_action/tagging.GetResources_1.json
@@ -0,0 +1,18 @@
+{
+    "status_code": 200,
+    "data": {
+        "PaginationToken": "",
+        "ResourceTagMappingList": [],
+        "ResponseMetadata": {
+            "RequestId": "15b6d484-217e-11e9-872f-c56b57c1cf00",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "x-amzn-requestid": "15b6d484-217e-11e9-872f-c56b57c1cf00",
+                "content-type": "application/x-amz-json-1.1",
+                "content-length": "230",
+                "date": "Sat, 26 Jan 2019 15:21:42 GMT"
+            },
+            "RetryAttempts": 0
+        }
+    }
+}
diff --git a/tests/data/placebo/test_dynamodb_mark/dynamodb.ListTagsOfResource_1.json b/tests/data/placebo/test_dynamodb_mark/dynamodb.ListTagsOfResource_1.json
--- a/tests/data/placebo/test_dynamodb_mark/dynamodb.ListTagsOfResource_1.json
+++ b/tests/data/placebo/test_dynamodb_mark/dynamodb.ListTagsOfResource_1.json
@@ -4,17 +4,22 @@
         "ResponseMetadata": {
             "RetryAttempts": 0, 
             "HTTPStatusCode": 200, 
-            "RequestId": "IF5IL5I0JQA7E3HIL0CA6F27ERVV4KQNSO5AEMVJF66Q9ASUAAJG", 
+            "RequestId": "V1QVBPF0LUUBBM9MQIDKSNM13NVV4KQNSO5AEMVJF66Q9ASUAAJG", 
             "HTTPHeaders": {
-                "x-amzn-requestid": "IF5IL5I0JQA7E3HIL0CA6F27ERVV4KQNSO5AEMVJF66Q9ASUAAJG", 
-                "content-length": "11", 
+                "x-amzn-requestid": "V1QVBPF0LUUBBM9MQIDKSNM13NVV4KQNSO5AEMVJF66Q9ASUAAJG", 
+                "content-length": "88", 
                 "server": "Server", 
                 "connection": "keep-alive", 
-                "x-amz-crc32": "1756070277", 
-                "date": "Fri, 08 Jun 2018 23:17:25 GMT", 
+                "x-amz-crc32": "547662945", 
+                "date": "Fri, 08 Jun 2018 23:17:26 GMT", 
                 "content-type": "application/x-amz-json-1.0"
             }
         }, 
-        "Tags": []
+        "Tags": [
+            {
+                "Value": "Resource does not meet policy: delete@2018/06/08", 
+                "Key": "test_tag"
+            }
+        ]
     }
 }
\ No newline at end of file
diff --git a/tests/data/placebo/test_dynamodb_mark/dynamodb.ListTagsOfResource_2.json b/tests/data/placebo/test_dynamodb_mark/dynamodb.ListTagsOfResource_2.json
deleted file mode 100644
--- a/tests/data/placebo/test_dynamodb_mark/dynamodb.ListTagsOfResource_2.json
+++ /dev/null
@@ -1,25 +0,0 @@
-{
-    "status_code": 200, 
-    "data": {
-        "ResponseMetadata": {
-            "RetryAttempts": 0, 
-            "HTTPStatusCode": 200, 
-            "RequestId": "V1QVBPF0LUUBBM9MQIDKSNM13NVV4KQNSO5AEMVJF66Q9ASUAAJG", 
-            "HTTPHeaders": {
-                "x-amzn-requestid": "V1QVBPF0LUUBBM9MQIDKSNM13NVV4KQNSO5AEMVJF66Q9ASUAAJG", 
-                "content-length": "88", 
-                "server": "Server", 
-                "connection": "keep-alive", 
-                "x-amz-crc32": "547662945", 
-                "date": "Fri, 08 Jun 2018 23:17:26 GMT", 
-                "content-type": "application/x-amz-json-1.0"
-            }
-        }, 
-        "Tags": [
-            {
-                "Value": "Resource does not meet policy: delete@2018/06/08", 
-                "Key": "test_tag"
-            }
-        ]
-    }
-}
\ No newline at end of file
diff --git a/tests/data/placebo/test_dynamodb_mark/tagging.GetResources_1.json b/tests/data/placebo/test_dynamodb_mark/tagging.GetResources_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_dynamodb_mark/tagging.GetResources_1.json
@@ -0,0 +1,18 @@
+{
+    "status_code": 200,
+    "data": {
+        "PaginationToken": "",
+        "ResourceTagMappingList": [],
+        "ResponseMetadata": {
+            "RequestId": "15b6d484-217e-11e9-872f-c56b57c1cf00",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "x-amzn-requestid": "15b6d484-217e-11e9-872f-c56b57c1cf00",
+                "content-type": "application/x-amz-json-1.1",
+                "content-length": "230",
+                "date": "Sat, 26 Jan 2019 15:21:42 GMT"
+            },
+            "RetryAttempts": 0
+        }
+    }
+}
diff --git a/tests/data/placebo/test_dynamodb_table/dynamodb.DescribeTable_1.json b/tests/data/placebo/test_dynamodb_table/dynamodb.DescribeTable_1.json
--- a/tests/data/placebo/test_dynamodb_table/dynamodb.DescribeTable_1.json
+++ b/tests/data/placebo/test_dynamodb_table/dynamodb.DescribeTable_1.json
@@ -1,43 +1,72 @@
 {
-    "status_code": 200, 
+    "status_code": 200,
     "data": {
         "Table": {
-            "TableArn": "arn:aws:dynamodb:us-west-2:644160558196:table/rolltop", 
             "AttributeDefinitions": [
                 {
-                    "AttributeName": "customer_id", 
+                    "AttributeName": "test",
                     "AttributeType": "S"
                 }
-            ], 
-            "ProvisionedThroughput": {
-                "NumberOfDecreasesToday": 0, 
-                "WriteCapacityUnits": 5, 
-                "ReadCapacityUnits": 5
-            }, 
-            "TableSizeBytes": 0, 
-            "TableName": "rolltop", 
-            "TableStatus": "ACTIVE", 
+            ],
+            "TableName": "test-table-kms-filter",
             "KeySchema": [
                 {
-                    "KeyType": "HASH", 
-                    "AttributeName": "customer_id"
+                    "AttributeName": "test",
+                    "KeyType": "HASH"
                 }
-            ], 
-            "ItemCount": 0, 
+            ],
+            "TableStatus": "ACTIVE",
             "CreationDateTime": {
-                "hour": 7, 
-                "__class__": "datetime", 
-                "month": 6, 
-                "second": 39, 
-                "microsecond": 637000, 
-                "year": 2016, 
-                "day": 26, 
-                "minute": 20
+                "__class__": "datetime",
+                "year": 2019,
+                "month": 1,
+                "day": 15,
+                "hour": 18,
+                "minute": 6,
+                "second": 37,
+                "microsecond": 198000
+            },
+            "ProvisionedThroughput": {
+                "NumberOfDecreasesToday": 0,
+                "ReadCapacityUnits": 0,
+                "WriteCapacityUnits": 0
+            },
+            "TableSizeBytes": 0,
+            "ItemCount": 0,
+            "TableArn": "arn:aws:dynamodb:us-east-1:644160558196:table/test-table-kms-filter",
+            "TableId": "561704a8-1a45-4a47-b2a6-71044bbe2807",
+            "BillingModeSummary": {
+                "BillingMode": "PAY_PER_REQUEST",
+                "LastUpdateToPayPerRequestDateTime": {
+                    "__class__": "datetime",
+                    "year": 2019,
+                    "month": 1,
+                    "day": 15,
+                    "hour": 18,
+                    "minute": 6,
+                    "second": 37,
+                    "microsecond": 198000
+                }
+            },
+            "SSEDescription": {
+                "Status": "ENABLED",
+                "SSEType": "KMS",
+                "KMSMasterKeyArn": "arn:aws:kms:us-east-1:644160558196:key/8785aeb9-a616-4e2b-bbd3-df3cde76bcc5"
             }
-        }, 
+        },
         "ResponseMetadata": {
-            "HTTPStatusCode": 200, 
-            "RequestId": "JJVG5SPOKF7HD096BRKVUVNIBFVV4KQNSO5AEMVJF66Q9ASUAAJG"
+            "RequestId": "25MTFJSDO607HHVH5R12N164KVVV4KQNSO5AEMVJF66Q9ASUAAJG",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "server": "Server",
+                "date": "Sat, 26 Jan 2019 15:21:42 GMT",
+                "content-type": "application/x-amz-json-1.0",
+                "content-length": "875",
+                "connection": "keep-alive",
+                "x-amzn-requestid": "25MTFJSDO607HHVH5R12N164KVVV4KQNSO5AEMVJF66Q9ASUAAJG",
+                "x-amz-crc32": "1592222704"
+            },
+            "RetryAttempts": 0
         }
     }
 }
\ No newline at end of file
diff --git a/tests/data/placebo/test_dynamodb_table/dynamodb.ListTables_1.json b/tests/data/placebo/test_dynamodb_table/dynamodb.ListTables_1.json
--- a/tests/data/placebo/test_dynamodb_table/dynamodb.ListTables_1.json
+++ b/tests/data/placebo/test_dynamodb_table/dynamodb.ListTables_1.json
@@ -1,12 +1,22 @@
 {
-    "status_code": 200, 
+    "status_code": 200,
     "data": {
-        "ResponseMetadata": {
-            "HTTPStatusCode": 200, 
-            "RequestId": "1K86DP6MB03RQURBT2423496GNVV4KQNSO5AEMVJF66Q9ASUAAJG"
-        }, 
         "TableNames": [
-            "rolltop"
-        ]
+            "test-table-kms-filter"
+        ],
+        "ResponseMetadata": {
+            "RequestId": "4MQNNJ5D2EMFPK3L56O63JLH4VVV4KQNSO5AEMVJF66Q9ASUAAJG",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "server": "Server",
+                "date": "Sat, 26 Jan 2019 15:21:42 GMT",
+                "content-type": "application/x-amz-json-1.0",
+                "content-length": "40",
+                "connection": "keep-alive",
+                "x-amzn-requestid": "4MQNNJ5D2EMFPK3L56O63JLH4VVV4KQNSO5AEMVJF66Q9ASUAAJG",
+                "x-amz-crc32": "3417179002"
+            },
+            "RetryAttempts": 0
+        }
     }
 }
\ No newline at end of file
diff --git a/tests/data/placebo/test_dynamodb_table/dynamodb.ListTagsOfResource_1.json b/tests/data/placebo/test_dynamodb_table/dynamodb.ListTagsOfResource_1.json
deleted file mode 100644
--- a/tests/data/placebo/test_dynamodb_table/dynamodb.ListTagsOfResource_1.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
-    "status_code": 200, 
-    "data": {
-        "ResponseMetadata": {
-            "RetryAttempts": 0, 
-            "HTTPStatusCode": 200, 
-            "RequestId": "57BIJH9PBVC3ERAISL5M6I2DBNVV4KQNSO5AEMVJF66Q9ASUAAJG", 
-            "HTTPHeaders": {
-                "x-amzn-requestid": "57BIJH9PBVC3ERAISL5M6I2DBNVV4KQNSO5AEMVJF66Q9ASUAAJG", 
-                "date": "Tue, 31 Jan 2017 19:34:51 GMT", 
-                "content-length": "160", 
-                "content-type": "application/x-amz-json-1.0", 
-                "x-amz-crc32": "2210834338"
-            }
-        }, 
-        "Tags": [
-            {
-                "Value": "test_value", 
-                "Key": "test_key"
-            }
-        ]
-    }
-}
\ No newline at end of file
diff --git a/tests/data/placebo/test_dynamodb_table/tagging.GetResources_1.json b/tests/data/placebo/test_dynamodb_table/tagging.GetResources_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_dynamodb_table/tagging.GetResources_1.json
@@ -0,0 +1,36 @@
+{
+    "status_code": 200,
+    "data": {
+        "PaginationToken": "",
+        "ResourceTagMappingList": [
+            {
+                "ResourceARN": "arn:aws:dynamodb:us-east-1:644160558196:table/test-table-kms-filter",
+                "Tags": [
+                    {
+                        "Key": "App",
+                        "Value": "Foo"
+                    },
+                    {
+                        "Key": "Env",
+                        "Value": "Dev"
+                    },
+                    {
+                        "Key": "Owner",
+                        "Value": "Bar"
+                    }
+                ]
+            }
+        ],
+        "ResponseMetadata": {
+            "RequestId": "15b6d484-217e-11e9-872f-c56b57c1cf00",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "x-amzn-requestid": "15b6d484-217e-11e9-872f-c56b57c1cf00",
+                "content-type": "application/x-amz-json-1.1",
+                "content-length": "230",
+                "date": "Sat, 26 Jan 2019 15:21:42 GMT"
+            },
+            "RetryAttempts": 0
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_dynamodb_tag/tagging.GetResources_1.json b/tests/data/placebo/test_dynamodb_tag/tagging.GetResources_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_dynamodb_tag/tagging.GetResources_1.json
@@ -0,0 +1,18 @@
+{
+    "status_code": 200,
+    "data": {
+        "PaginationToken": "",
+        "ResourceTagMappingList": [],
+        "ResponseMetadata": {
+            "RequestId": "15b6d484-217e-11e9-872f-c56b57c1cf00",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "x-amzn-requestid": "15b6d484-217e-11e9-872f-c56b57c1cf00",
+                "content-type": "application/x-amz-json-1.1",
+                "content-length": "230",
+                "date": "Sat, 26 Jan 2019 15:21:42 GMT"
+            },
+            "RetryAttempts": 0
+        }
+    }
+}
diff --git a/tests/data/placebo/test_dynamodb_tag_filter/tagging.GetResources_1.json b/tests/data/placebo/test_dynamodb_tag_filter/tagging.GetResources_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_dynamodb_tag_filter/tagging.GetResources_1.json
@@ -0,0 +1,28 @@
+{
+    "status_code": 200,
+    "data": {
+        "PaginationToken": "",
+        "ResourceTagMappingList": [
+            {
+                "ResourceARN": "arn:aws:dynamodb:us-west-2:644160558196:table/rolltop",
+                "Tags": [
+                    {
+                        "Key": "test_key",
+                        "Value": "test_value"
+                    }
+                ]
+            }
+        ],
+        "ResponseMetadata": {
+            "RequestId": "15b6d484-217e-11e9-872f-c56b57c1cf00",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "x-amzn-requestid": "15b6d484-217e-11e9-872f-c56b57c1cf00",
+                "content-type": "application/x-amz-json-1.1",
+                "content-length": "230",
+                "date": "Sat, 26 Jan 2019 15:21:42 GMT"
+            },
+            "RetryAttempts": 0
+        }
+    }
+}
diff --git a/tests/data/placebo/test_dynamodb_unmark/dynamodb.ListTagsOfResource_1.json b/tests/data/placebo/test_dynamodb_unmark/dynamodb.ListTagsOfResource_1.json
--- a/tests/data/placebo/test_dynamodb_unmark/dynamodb.ListTagsOfResource_1.json
+++ b/tests/data/placebo/test_dynamodb_unmark/dynamodb.ListTagsOfResource_1.json
@@ -14,10 +14,6 @@
             }
         }, 
         "Tags": [
-            {
-                "Value": "test_value", 
-                "Key": "test_key"
-            }
         ]
     }
-}
\ No newline at end of file
+}
diff --git a/tests/data/placebo/test_dynamodb_unmark/tagging.GetResources_1.json b/tests/data/placebo/test_dynamodb_unmark/tagging.GetResources_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_dynamodb_unmark/tagging.GetResources_1.json
@@ -0,0 +1,28 @@
+{
+    "status_code": 200,
+    "data": {
+        "PaginationToken": "",
+        "ResourceTagMappingList": [
+            {
+                "ResourceARN": "arn:aws:dynamodb:us-west-2:644160558196:table/rolltop",
+                "Tags": [
+                    {
+                        "Key": "test_key",
+                        "Value": "test_value"
+                    }
+                ]
+            }
+        ],
+        "ResponseMetadata": {
+            "RequestId": "15b6d484-217e-11e9-872f-c56b57c1cf00",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "x-amzn-requestid": "15b6d484-217e-11e9-872f-c56b57c1cf00",
+                "content-type": "application/x-amz-json-1.1",
+                "content-length": "230",
+                "date": "Sat, 26 Jan 2019 15:21:42 GMT"
+            },
+            "RetryAttempts": 0
+        }
+    }
+}
diff --git a/tests/test_dynamodb.py b/tests/test_dynamodb.py
--- a/tests/test_dynamodb.py
+++ b/tests/test_dynamodb.py
@@ -31,7 +31,7 @@ def test_resources(self):
         )
         resources = p.run()
         self.assertEqual(len(resources), 1)
-        self.assertEqual(resources[0]["TableName"], "rolltop")
+        self.assertEqual(resources[0]["TableName"], "test-table-kms-filter")
         self.assertEqual(resources[0]["TableStatus"], "ACTIVE")
 
     def test_invoke_action(self):
@@ -72,7 +72,8 @@ def test_tag_filter(self):
                 "filters": [{"tag:test_key": "test_value"}],
             },
             session_factory=session_factory,
-        )
+            config={'region': 'us-west-2', 'account_id': '644160558196'})
+
         resources = p.run()
         self.assertEqual(len(resources), 1)
         arn = resources[0]["TableArn"]