Repository: cloud-custodian/cloud-custodian

Issue:
Kafka emails causing c7n mailer to error out
**Describe the bug**
Any emails coming from the kafka resources are causing the mailer to error out due to what appears to be a get tags issue.

**To Reproduce**
any notify action appears to trigger this with the kafka resourcde

**Expected behavior**
Expecting the emails to send


See gist for policy and error
 https://gist.github.com/jtroberts83/68b2f98bbd4f5255821b13160eebfb23


Discussion:
thanks for the bug report, afaics the service started adding tags, in a different format then what we expect, and we should a) modify how it looks to the rest of custodian b) update mailer to support both styles in case it happens again for a different resource.

Resolving patch:
diff --git a/c7n/resources/kafka.py b/c7n/resources/kafka.py
--- a/c7n/resources/kafka.py
+++ b/c7n/resources/kafka.py
@@ -32,6 +32,16 @@ class resource_type(TypeInfo):
         filter_name = 'ClusterNameFilter'
         filter_type = 'scalar'
 
+    def augment(self, resources):
+        for r in resources:
+            if 'Tags' not in r:
+                continue
+            tags = []
+            for k, v in r['Tags'].items():
+                tags.append({'Key': k, 'Value': v})
+            r['Tags'] = tags
+        return resources
+
 
 @Kafka.filter_registry.register('security-group')
 class KafkaSGFilter(SecurityGroupFilter):
diff --git a/tools/c7n_mailer/c7n_mailer/utils.py b/tools/c7n_mailer/c7n_mailer/utils.py
--- a/tools/c7n_mailer/c7n_mailer/utils.py
+++ b/tools/c7n_mailer/c7n_mailer/utils.py
@@ -91,7 +91,10 @@ def get_rendered_jinja(
 def get_resource_tag_targets(resource, target_tag_keys):
     if 'Tags' not in resource:
         return []
-    tags = {tag['Key']: tag['Value'] for tag in resource['Tags']}
+    if isinstance(resource['Tags'], dict):
+        tags = resource['Tags']
+    else:
+        tags = {tag['Key']: tag['Value'] for tag in resource['Tags']}
     targets = []
     for target_tag_key in target_tag_keys:
         if target_tag_key in tags:


Test patch:
diff --git a/tests/data/kafka.json b/tests/data/kafka.json
new file mode 100644
--- /dev/null
+++ b/tests/data/kafka.json
@@ -0,0 +1,55 @@
+{
+    "BrokerNodeGroupInfo": {
+        "BrokerAZDistribution": "DEFAULT",
+        "ClientSubnets": [
+            "subnet-8136ffcb",
+            "subnet-bbb8bde6"
+        ],
+        "InstanceType": "kafka.m5.large",
+        "SecurityGroups": [
+            "sg-65878213"
+        ],
+        "StorageInfo": {
+            "EbsStorageInfo": {
+                "VolumeSize": 1
+            }
+        }
+    },
+    "ClusterArn": "arn:aws:kafka:us-east-1:123456789000:cluster/Test-ACCS-Enforce-Encryption/c9739447-7d75-45dc-8709-48f664a612ab-5",
+    "ClusterName": "Test-ACCS-Enforce-Encryption",
+    "CreationTime": "2020-02-28T19:49:13.084000+00:00",
+    "CurrentBrokerSoftwareInfo": {
+        "KafkaVersion": "2.2.1"
+    },
+    "CurrentVersion": "K2EUQ1WTGCTBG2",
+    "EncryptionInfo": {
+        "EncryptionAtRest": {
+            "DataVolumeKMSKeyId": "arn:aws:kms:us-east-1:123456789000:key/d4c21e0d-8b16-4f08-901c-b76970685858"
+        },
+        "EncryptionInTransit": {
+            "ClientBroker": "PLAINTEXT",
+            "InCluster": false
+        }
+    },
+    "EnhancedMonitoring": "DEFAULT",
+    "OpenMonitoring": {
+        "Prometheus": {
+            "JmxExporter": {
+                "EnabledInBroker": false
+            },
+            "NodeExporter": {
+                "EnabledInBroker": false
+            }
+        }
+    },
+    "NumberOfBrokerNodes": 2,
+    "State": "ACTIVE",
+    "Tags": {
+        "ResourceContact": "ouremailaddress@company.com"
+    },
+    "ZookeeperConnectString": "z-2.test-accs-enforce-encr.tnr2qf.c5.kafka.us-east-1.amazonaws.com:2181,z-3.test-accs-enforce-encr.tnr2qf.c5.kafka.us-east-1.amazonaws.com:2181,z-1.test-accs-enforce-encr.tnr2qf.c5.kafka.us-east-1.amazonaws.com:2181",
+    "c7n:MatchedFilters": [
+        "EncryptionInfo.EncryptionInTransit.ClientBroker",
+        "CreationTime"
+    ]
+}
diff --git a/tests/test_kafka.py b/tests/test_kafka.py
--- a/tests/test_kafka.py
+++ b/tests/test_kafka.py
@@ -1,10 +1,18 @@
 import time
 
-from .common import BaseTest
+from .common import BaseTest, load_data
 
 
 class KafkaTest(BaseTest):
 
+    def test_tag_normalize(self):
+        p = self.load_policy({'name': 'kafka', 'resource': 'aws.kafka'})
+        resource = load_data('kafka.json')
+        results = p.resource_manager.augment([resource])
+        self.assertEqual(
+            results[0]['Tags'],
+            [{'Key': 'ResourceContact', 'Value': 'ouremailaddress@company.com'}])
+
     def test_subnet_filter(self):
         factory = self.replay_flight_data('test_kafka_subnet_filter')
         p = self.load_policy({
diff --git a/tools/c7n_mailer/tests/test_utils.py b/tools/c7n_mailer/tests/test_utils.py
--- a/tools/c7n_mailer/tests/test_utils.py
+++ b/tools/c7n_mailer/tests/test_utils.py
@@ -26,6 +26,22 @@ def test_strip_prefix(self):
         self.assertEqual(utils.strip_prefix('', 'aws.'), '')
 
 
+class GetResourceTagTargets(unittest.TestCase):
+
+    def test_target_tag_list(self):
+        self.assertEqual(
+            utils.get_resource_tag_targets(
+                {'Tags': [{'Key': 'Creator', 'Value': 'alice'}]},
+                ['Creator']),
+            ['alice'])
+
+    def test_target_tag_map(self):
+        r = {'Tags': {'Creator': 'Bob'}}
+        self.assertEqual(
+            utils.get_resource_tag_targets(r, ['Creator']),
+            ['Bob'])
+
+
 class ResourceFormat(unittest.TestCase):
 
     def test_efs(self):