1# Copyright The Cloud Custodian Authors.
2# SPDX-License-Identifier: Apache-2.0
3import itertools
4
5from c7n.utils import local_session, chunks, type_schema
6from .core import Filter
7from c7n.manager import resources
8
9
10class HealthEventFilter(Filter):
11 """Check if there are operations health events (phd) related to the resources
12
13 https://aws.amazon.com/premiumsupport/technology/personal-health-dashboard/
14
15 Health events are stored as annotation on a resource.
16
17 Custodian also supports responding to phd events via a lambda execution mode.
18 """
19 schema_alias = True
20 schema = type_schema(
21 'health-event',
22 types={'type': 'array', 'items': {'type': 'string'}},
23 category={'type': 'array', 'items': {
24 'enum': ['issue', 'accountNotification', 'scheduledChange']}},
25 statuses={'type': 'array', 'items': {
26 'type': 'string',
27 'enum': ['open', 'upcoming', 'closed']
28 }})
29 permissions = ('health:DescribeEvents', 'health:DescribeAffectedEntities',
30 'health:DescribeEventDetails')
31
32 def process(self, resources, event=None):
33 client = local_session(self.manager.session_factory).client(
34 'health', region_name='us-east-1')
35 f = self.get_filter_parameters()
36 if self.manager.data['resource'] in {'app-elb'}:
37 id_attr = self.manager.get_model().name
38 else:
39 id_attr = self.manager.get_model().id
40 resource_map = {r[id_attr]: r for r in resources}
41 found = set()
42 seen = set()
43
44 for resource_set in chunks(resource_map.keys(), 99):
45 f['entityValues'] = resource_set
46 events = client.describe_events(filter=f)['events']
47 events = [e for e in events if e['arn'] not in seen]
48 entities = self.process_event(client, events)
49
50 event_map = {e['arn']: e for e in events}
51 for e in entities:
52 rid = e['entityValue']
53 if rid not in resource_map:
54 continue
55 resource_map[rid].setdefault(
56 'c7n:HealthEvent', []).append(event_map[e['eventArn']])
57 found.add(rid)
58 seen.update(event_map.keys())
59 return [resource_map[resource_id] for resource_id in found]
60
61 def get_filter_parameters(self):
62 phd_svc_name_map = {
63 'app-elb': 'ELASTICLOADBALANCING',
64 'ebs': 'EBS',
65 'efs': 'ELASTICFILESYSTEM',
66 'elb': 'ELASTICLOADBALANCING',
67 'emr': 'ELASTICMAPREDUCE'
68 }
69 m = self.manager
70 service = phd_svc_name_map.get(m.data['resource'], m.get_model().service.upper())
71 f = {'services': [service],
72 'regions': [self.manager.config.region, 'global'],
73 'eventStatusCodes': self.data.get(
74 'statuses', ['open', 'upcoming'])}
75 if self.data.get('types'):
76 f['eventTypeCodes'] = self.data.get('types')
77 return f
78
79 def process_event(self, client, health_events):
80 entities = []
81 for event_set in chunks(health_events, 10):
82 event_map = {e['arn']: e for e in event_set}
83 event_arns = list(event_map.keys())
84 for d in client.describe_event_details(
85 eventArns=event_arns).get('successfulSet', ()):
86 event_map[d['event']['arn']]['Description'] = d[
87 'eventDescription']['latestDescription']
88 paginator = client.get_paginator('describe_affected_entities')
89 entities.extend(list(itertools.chain(
90 *[p['entities'] for p in paginator.paginate(
91 filter={'eventArns': event_arns})])))
92 return entities
93
94 @classmethod
95 def register_resources(klass, registry, resource_class):
96 """ meta model subscriber on resource registration.
97
98 We watch for PHD event that provides affected entities and register
99 the health-event filter to the resources.
100 """
101 services = {'acm-certificate', 'directconnect', 'dms-instance', 'directory', 'ec2',
102 'dynamodb-table', 'cache-cluster', 'efs', 'app-elb', 'elb', 'emr', 'rds',
103 'storage-gateway'}
104 if resource_class.type in services:
105 resource_class.filter_registry.register('health-event', klass)
106
107
108resources.subscribe(HealthEventFilter.register_resources)