1# Copyright The Cloud Custodian Authors.
2# SPDX-License-Identifier: Apache-2.0
3import itertools
4import re
5from collections import defaultdict
6from concurrent.futures import as_completed
7from datetime import datetime, timedelta
8
9from c7n.actions import BaseAction
10from c7n.exceptions import PolicyValidationError
11from c7n.filters import Filter, MetricsFilter
12from c7n.filters.core import parse_date, ValueFilter
13from c7n.filters.iamaccess import CrossAccountAccessFilter
14from c7n.filters.kms import KmsRelatedFilter
15from c7n.manager import resources
16from c7n.query import (
17 QueryResourceManager,
18 TypeInfo, DescribeSource, ConfigSource, DescribeWithResourceTags)
19from c7n.resolver import ValuesFrom
20from c7n.tags import universal_augment
21from c7n.utils import type_schema, local_session, chunks, get_retry, jmespath_search
22
23
24class DescribeAlarm(DescribeSource):
25 def augment(self, resources):
26 return universal_augment(self.manager, super().augment(resources))
27
28
29@resources.register('alarm')
30class Alarm(QueryResourceManager):
31 class resource_type(TypeInfo):
32 service = 'cloudwatch'
33 arn_type = 'alarm'
34 enum_spec = ('describe_alarms', 'MetricAlarms', None)
35 id = 'AlarmName'
36 arn = 'AlarmArn'
37 filter_name = 'AlarmNames'
38 filter_type = 'list'
39 name = 'AlarmName'
40 date = 'AlarmConfigurationUpdatedTimestamp'
41 cfn_type = config_type = 'AWS::CloudWatch::Alarm'
42 universal_taggable = object()
43 permissions_augment = ("cloudwatch:ListTagsForResource",)
44
45 source_mapping = {
46 'describe': DescribeAlarm,
47 'config': ConfigSource
48 }
49
50 retry = staticmethod(get_retry(('Throttled',)))
51
52
53@Alarm.action_registry.register('delete')
54class AlarmDelete(BaseAction):
55 """Delete a cloudwatch alarm.
56
57 :example:
58
59 .. code-block:: yaml
60
61 policies:
62 - name: cloudwatch-delete-stale-alarms
63 resource: alarm
64 filters:
65 - type: value
66 value_type: age
67 key: StateUpdatedTimestamp
68 value: 30
69 op: ge
70 - StateValue: INSUFFICIENT_DATA
71 actions:
72 - delete
73 """
74
75 schema = type_schema('delete')
76 permissions = ('cloudwatch:DeleteAlarms',)
77
78 def process(self, resources):
79 client = local_session(
80 self.manager.session_factory).client('cloudwatch')
81
82 for resource_set in chunks(resources, size=100):
83 self.manager.retry(
84 client.delete_alarms,
85 AlarmNames=[r['AlarmName'] for r in resource_set])
86
87
88@Alarm.filter_registry.register('is-composite-child')
89class IsCompositeChild(Filter):
90 schema = type_schema('is-composite-child', state={"type": "boolean"})
91 permissions = ('cloudwatch:DescribeAlarms',)
92
93 def process(self, resources, event=None):
94 state = self.data.get("state", True)
95 # Get the composite alarms since filtered out in enum_spec
96 composite_alarms = self.manager.get_resource_manager("composite-alarm").resources()
97 composite_alarm_rules = jmespath_search('[].AlarmRule', composite_alarms)
98
99 child_alarm_names = set()
100 # Loop through, find child alarm names
101 for rule in composite_alarm_rules:
102 names = self.extract_alarm_names_from_rule(rule)
103 child_alarm_names.update(names)
104
105 if state:
106 # If we want to filter out alarms that are a child of a composite alarm
107 return [r for r in resources if r['AlarmName'] in child_alarm_names]
108
109 return [r for r in resources if r['AlarmName'] not in child_alarm_names]
110
111 def extract_alarm_names_from_rule(self, rule):
112 # Check alarm references (OK/ALARM/INSUFFICIENT_DATA)
113 pattern = r"\b(?:ALARM|OK|INSUFFICIENT_DATA)\s*\(\s*([^\)]+)\s*\)"
114 matches = re.findall(pattern, rule)
115 return set(matches)
116
117
118@resources.register('composite-alarm')
119class CompositeAlarm(QueryResourceManager):
120
121 class resource_type(TypeInfo):
122 service = 'cloudwatch'
123 arn_type = 'alarm'
124 enum_spec = ('describe_alarms', 'CompositeAlarms', {'AlarmTypes': ['CompositeAlarm']})
125 id = name = 'AlarmName'
126 arn = 'AlarmArn'
127 date = 'AlarmConfigurationUpdatedTimestamp'
128 cfn_type = 'AWS::CloudWatch::CompositeAlarm'
129 universal_taggable = object()
130
131 augment = universal_augment
132
133 retry = staticmethod(get_retry(('Throttled',)))
134
135
136@CompositeAlarm.action_registry.register('delete')
137class CompositeAlarmDelete(BaseAction):
138 """Delete a cloudwatch composite alarm.
139
140 :example:
141
142 .. code-block:: yaml
143
144 policies:
145 - name: cloudwatch-delete-composite-alarms
146 resource: aws.composite-alarm
147 filters:
148 - type: value
149 value_type: age
150 key: StateUpdatedTimestamp
151 value: 30
152 op: ge
153 - StateValue: INSUFFICIENT_DATA
154 actions:
155 - delete
156 """
157
158 schema = type_schema('delete')
159 permissions = ('cloudwatch:DeleteAlarms',)
160
161 def process(self, resources):
162 client = local_session(
163 self.manager.session_factory).client('cloudwatch')
164
165 for resource_set in chunks(resources, size=100):
166 self.manager.retry(
167 client.delete_alarms,
168 AlarmNames=[r['AlarmName'] for r in resource_set])
169
170
171@resources.register('log-group')
172class LogGroup(QueryResourceManager):
173 class resource_type(TypeInfo):
174 service = 'logs'
175 arn_type = 'log-group'
176 enum_spec = ('describe_log_groups', 'logGroups', None)
177 id = name = 'logGroupName'
178 arn = 'arn' # see get-arns override re attribute usage
179 filter_name = 'logGroupNamePrefix'
180 filter_type = 'scalar'
181 dimension = 'LogGroupName'
182 date = 'creationTime'
183 universal_taggable = True
184 cfn_type = 'AWS::Logs::LogGroup'
185 permissions_augment = ("logs:ListTagsForResource",)
186
187 augment = universal_augment
188
189 def get_arns(self, resources):
190 # log group arn in resource describe has ':*' suffix, not all
191 # apis can use that form, so normalize to standard arn.
192 return [r['arn'][:-2] for r in resources]
193
194
195@resources.register('insight-rule')
196class InsightRule(QueryResourceManager):
197 class resource_type(TypeInfo):
198 service = 'cloudwatch'
199 arn_type = 'insight-rule'
200 enum_spec = ('describe_insight_rules', 'InsightRules', None)
201 name = id = 'Name'
202 universal_taggable = object()
203 permission_augment = ('cloudWatch::ListTagsForResource',)
204 cfn_type = 'AWS::CloudWatch::InsightRule'
205
206 def augment(self, rules):
207 client = local_session(self.session_factory).client('cloudwatch')
208
209 def _add_tags(r):
210 arn = self.generate_arn(r['Name'])
211 r['Tags'] = client.list_tags_for_resource(
212 ResourceARN=arn).get('Tags', [])
213 return r
214
215 return list(map(_add_tags, rules))
216
217
218@InsightRule.action_registry.register('disable')
219class InsightRuleDisable(BaseAction):
220 """Disable a cloudwatch contributor insight rule.
221
222 :example:
223
224 .. code-block:: yaml
225
226 policies:
227 - name: cloudwatch-disable-insight-rule
228 resource: insight-rule
229 filters:
230 - type: value
231 key: State
232 value: ENABLED
233 op: eq
234 actions:
235 - disable
236 """
237
238 schema = type_schema('disable')
239 permissions = ('cloudwatch:DisableInsightRules',)
240
241 def process(self, resources):
242 client = local_session(
243 self.manager.session_factory).client('cloudwatch')
244
245 for resource_set in chunks(resources, size=100):
246 self.manager.retry(
247 client.disable_insight_rules,
248 RuleNames=[r['Name'] for r in resource_set])
249
250
251@InsightRule.action_registry.register('delete')
252class InsightRuleDelete(BaseAction):
253 """Delete a cloudwatch contributor insight rule
254
255 :example:
256
257 .. code-block:: yaml
258
259 policies:
260 - name: cloudwatch-delete-insight-rule
261 resource: insight-rule
262 filters:
263 - type: value
264 key: State
265 value: ENABLED
266 op: eq
267 actions:
268 - delete
269 """
270
271 schema = type_schema('delete')
272 permissions = ('cloudwatch:DeleteInsightRules',)
273
274 def process(self, resources):
275 client = local_session(
276 self.manager.session_factory).client('cloudwatch')
277
278 for resource_set in chunks(resources, size=100):
279 self.manager.retry(
280 client.delete_insight_rules,
281 RuleNames=[r['Name'] for r in resource_set])
282
283
284@LogGroup.filter_registry.register('metrics')
285class LogGroupMetrics(MetricsFilter):
286
287 def get_dimensions(self, resource):
288 return [{'Name': 'LogGroupName', 'Value': resource['logGroupName']}]
289
290
291@resources.register('log-metric')
292class LogMetric(QueryResourceManager):
293 class resource_type(TypeInfo):
294 service = 'logs'
295 enum_spec = ('describe_metric_filters', 'metricFilters', None)
296 arn = False
297 id = name = 'filterName'
298 date = 'creationTime'
299 cfn_type = 'AWS::Logs::MetricFilter'
300
301
302@LogMetric.filter_registry.register('alarm')
303class LogMetricAlarmFilter(ValueFilter):
304 """
305 Filter log metric filters based on associated alarms.
306
307 :example:
308
309 .. code-block:: yaml
310
311 policies:
312 - name: log-metrics-with-alarms
313 resource: aws.log-metric
314 filters:
315 - type: alarm
316 key: AlarmName
317 value: present
318 """
319
320 schema = type_schema('alarm', rinherit=ValueFilter.schema)
321 annotation_key = 'c7n:MetricAlarms'
322 FetchThreshold = 10 # below this number of resources, fetch alarms individually
323
324 def augment(self, resources):
325 """Add alarm details to log metric filter resources
326
327 This includes all alarms where the metric name and namespace match
328 a log metric filter's metric transformation.
329 """
330
331 if len(resources) < self.FetchThreshold:
332 client = local_session(self.manager.session_factory).client('cloudwatch')
333 for r in resources:
334 r[self.annotation_key] = list(itertools.chain(*(
335 self.manager.retry(
336 client.describe_alarms_for_metric,
337 Namespace=t['metricNamespace'],
338 MetricName=t['metricName'])['MetricAlarms']
339 for t in r.get('metricTransformations', ())
340 )))
341 else:
342 alarms = self.manager.get_resource_manager('aws.alarm').resources()
343
344 # We'll be matching resources to alarms based on namespace and
345 # metric name - this lookup table makes that smoother
346 alarms_by_metric = defaultdict(list)
347 for alarm in alarms:
348 alarms_by_metric[(alarm['Namespace'], alarm['MetricName'])].append(alarm)
349
350 for r in resources:
351 r[self.annotation_key] = list(itertools.chain(*(
352 alarms_by_metric.get((t['metricNamespace'], t['metricName']), [])
353 for t in r.get('metricTransformations', ())
354 )))
355
356 def get_permissions(self):
357 return [
358 *self.manager.get_resource_manager('aws.alarm').get_permissions(),
359 'cloudwatch:DescribeAlarmsForMetric'
360 ]
361
362 def process(self, resources, event=None):
363 self.augment(resources)
364
365 matched = []
366 for r in resources:
367 if any((self.match(alarm) for alarm in r[self.annotation_key])):
368 matched.append(r)
369 return matched
370
371
372@LogGroup.action_registry.register('retention')
373class Retention(BaseAction):
374 """Action to set the retention period (in days) for CloudWatch log groups
375
376 :example:
377
378 .. code-block:: yaml
379
380 policies:
381 - name: cloudwatch-set-log-group-retention
382 resource: log-group
383 actions:
384 - type: retention
385 days: 200
386 """
387
388 schema = type_schema('retention', days={'type': 'integer'})
389 permissions = ('logs:PutRetentionPolicy',)
390
391 def process(self, resources):
392 client = local_session(self.manager.session_factory).client('logs')
393 days = self.data['days']
394 for r in resources:
395 self.manager.retry(
396 client.put_retention_policy,
397 logGroupName=r['logGroupName'],
398 retentionInDays=days)
399
400
401@LogGroup.action_registry.register('delete')
402class Delete(BaseAction):
403 """
404
405 :example:
406
407 .. code-block:: yaml
408
409 policies:
410 - name: cloudwatch-delete-stale-log-group
411 resource: log-group
412 filters:
413 - type: last-write
414 days: 182.5
415 actions:
416 - delete
417 """
418
419 schema = type_schema('delete')
420 permissions = ('logs:DeleteLogGroup',)
421
422 def process(self, resources):
423 client = local_session(self.manager.session_factory).client('logs')
424 for r in resources:
425 try:
426 self.manager.retry(
427 client.delete_log_group, logGroupName=r['logGroupName'])
428 except client.exceptions.ResourceNotFoundException:
429 continue
430
431
432@LogGroup.filter_registry.register('last-write')
433class LastWriteDays(Filter):
434 """Filters CloudWatch log groups by last write
435
436 :example:
437
438 .. code-block:: yaml
439
440 policies:
441 - name: cloudwatch-stale-groups
442 resource: log-group
443 filters:
444 - type: last-write
445 days: 60
446 """
447
448 schema = type_schema(
449 'last-write', days={'type': 'number'})
450 permissions = ('logs:DescribeLogStreams',)
451
452 def process(self, resources, event=None):
453 client = local_session(self.manager.session_factory).client('logs')
454 self.date_threshold = parse_date(datetime.utcnow()) - timedelta(
455 days=self.data['days'])
456 return [r for r in resources if self.check_group(client, r)]
457
458 def check_group(self, client, group):
459 streams = self.manager.retry(
460 client.describe_log_streams,
461 logGroupName=group['logGroupName'],
462 orderBy='LastEventTime',
463 descending=True,
464 limit=3).get('logStreams')
465 group['streams'] = streams
466 if not streams:
467 last_timestamp = group['creationTime']
468 elif 'lastIngestionTime' in streams[0]:
469 last_timestamp = streams[0]['lastIngestionTime']
470 else:
471 last_timestamp = streams[0]['creationTime']
472
473 last_write = parse_date(last_timestamp)
474 group['lastWrite'] = last_write
475 return self.date_threshold > last_write
476
477
478@LogGroup.filter_registry.register('cross-account')
479class LogCrossAccountFilter(CrossAccountAccessFilter):
480 schema = type_schema(
481 'cross-account',
482 # white list accounts
483 whitelist_from=ValuesFrom.schema,
484 whitelist={'type': 'array', 'items': {'type': 'string'}})
485
486 permissions = ('logs:DescribeSubscriptionFilters',)
487
488 def process(self, resources, event=None):
489 client = local_session(self.manager.session_factory).client('logs')
490 accounts = self.get_accounts()
491 results = []
492 with self.executor_factory(max_workers=1) as w:
493 futures = []
494 for rset in chunks(resources, 50):
495 futures.append(
496 w.submit(
497 self.process_resource_set, client, accounts, rset))
498 for f in as_completed(futures):
499 if f.exception():
500 self.log.error(
501 "Error checking log groups cross-account %s",
502 f.exception())
503 continue
504 results.extend(f.result())
505 return results
506
507 def process_resource_set(self, client, accounts, resources):
508 results = []
509 for r in resources:
510 found = False
511 filters = self.manager.retry(
512 client.describe_subscription_filters,
513 logGroupName=r['logGroupName']).get('subscriptionFilters', ())
514 for f in filters:
515 if 'destinationArn' not in f:
516 continue
517 account_id = f['destinationArn'].split(':', 5)[4]
518 if account_id not in accounts:
519 r.setdefault('c7n:CrossAccountViolations', []).append(
520 account_id)
521 found = True
522 if found:
523 results.append(r)
524 return results
525
526
527@LogGroup.filter_registry.register('subscription-filter')
528class LogSubscriptionFilter(ValueFilter):
529 """Filters CloudWatch log groups by subscriptions
530
531 :example:
532
533 .. code-block:: yaml
534
535 policies:
536 - name: cloudwatch-groups-with-subscriptions
537 resource: log-group
538 filters:
539 - type: subscription-filter
540 key: destinationArn
541 value: arn:aws:lambda:us-east-1:123456789876:function:forwarder
542 """
543 schema = type_schema('subscription-filter', rinherit=ValueFilter.schema)
544 annotation_key = 'c7n:SubscriptionFilters'
545 permissions = ('logs:DescribeSubscriptionFilters',)
546
547 def process(self, resources, event=None):
548 client = local_session(self.manager.session_factory).client('logs')
549 results = []
550 for r in resources:
551 filters = self.manager.retry(
552 client.describe_subscription_filters,
553 logGroupName=r['logGroupName']).get('subscriptionFilters', ())
554 if not any(filters):
555 continue
556 for f in filters:
557 r.setdefault(self.annotation_key, []).append(f)
558 if (len(self.data) == 1) or any((self.match(sub) for sub in r[self.annotation_key])):
559 results.append(r)
560 return results
561
562
563@LogGroup.filter_registry.register('kms-key')
564class KmsFilter(KmsRelatedFilter):
565 RelatedIdsExpression = 'kmsKeyId'
566
567
568@LogGroup.action_registry.register('set-encryption')
569class EncryptLogGroup(BaseAction):
570 """Encrypt/Decrypt a log group
571
572 :example:
573
574 .. code-block:: yaml
575
576 policies:
577 - name: encrypt-log-group
578 resource: log-group
579 filters:
580 - kmsKeyId: absent
581 actions:
582 - type: set-encryption
583 kms-key: alias/mylogkey
584 state: True
585
586 - name: decrypt-log-group
587 resource: log-group
588 filters:
589 - kmsKeyId: kms:key:arn
590 actions:
591 - type: set-encryption
592 state: False
593 """
594 schema = type_schema(
595 'set-encryption',
596 **{'kms-key': {'type': 'string'},
597 'state': {'type': 'boolean'}})
598 permissions = (
599 'logs:AssociateKmsKey', 'logs:DisassociateKmsKey', 'kms:DescribeKey')
600
601 def validate(self):
602 if not self.data.get('state', True):
603 return self
604 key = self.data.get('kms-key', '')
605 if not key:
606 raise ValueError('Must specify either a KMS key ARN or Alias')
607 if 'alias/' not in key and ':key/' not in key:
608 raise PolicyValidationError(
609 "Invalid kms key format %s" % key)
610 return self
611
612 def resolve_key(self, key):
613 if not key:
614 return
615
616 # Qualified arn for key
617 if key.startswith('arn:') and ':key/' in key:
618 return key
619
620 # Alias
621 key = local_session(
622 self.manager.session_factory).client(
623 'kms').describe_key(
624 KeyId=key)['KeyMetadata']['Arn']
625 return key
626
627 def process(self, resources):
628 session = local_session(self.manager.session_factory)
629 client = session.client('logs')
630
631 state = self.data.get('state', True)
632 key = self.resolve_key(self.data.get('kms-key'))
633
634 for r in resources:
635 try:
636 if state:
637 client.associate_kms_key(
638 logGroupName=r['logGroupName'], kmsKeyId=key)
639 else:
640 client.disassociate_kms_key(logGroupName=r['logGroupName'])
641 except client.exceptions.ResourceNotFoundException:
642 continue
643
644
645@LogGroup.action_registry.register('put-subscription-filter')
646class SubscriptionFilter(BaseAction):
647 """Create/Update a subscription filter and associate with a log group
648
649 :example:
650
651 .. code-block:: yaml
652
653 policies:
654 - name: cloudwatch-put-subscription-filter
655 resource: log-group
656 actions:
657 - type: put-subscription-filter
658 filter_name: AllLambda
659 filter_pattern: ip
660 destination_arn: arn:aws:logs:us-east-1:1234567890:destination:lambda
661 distribution: Random
662 role_arn: "arn:aws:iam::{account_id}:role/testCrossAccountRole"
663 """
664 schema = type_schema(
665 'put-subscription-filter',
666 filter_name={'type': 'string'},
667 filter_pattern={'type': 'string'},
668 destination_arn={'type': 'string'},
669 distribution={'enum': ['Random', 'ByLogStream']},
670 role_arn={'type': 'string'},
671 required=['filter_name', 'destination_arn'])
672 permissions = ('logs:PutSubscriptionFilter',)
673
674 def process(self, resources):
675 session = local_session(self.manager.session_factory)
676 client = session.client('logs')
677 params = dict(
678 filterName=self.data.get('filter_name'),
679 filterPattern=self.data.get('filter_pattern', ''),
680 destinationArn=self.data.get('destination_arn'),
681 distribution=self.data.get('distribution', 'ByLogStream'))
682
683 if self.data.get('role_arn'):
684 params['roleArn'] = self.data.get('role_arn')
685
686 for r in resources:
687 client.put_subscription_filter(
688 logGroupName=r['logGroupName'], **params)
689
690
691@resources.register("cloudwatch-dashboard")
692class CloudWatchDashboard(QueryResourceManager):
693 class resource_type(TypeInfo):
694 service = "cloudwatch"
695 enum_spec = ('list_dashboards', 'DashboardEntries', None)
696 arn_type = "dashboard"
697 arn = "DashboardArn"
698 id = "DashboardName"
699 name = "DashboardName"
700 cfn_type = "AWS::CloudWatch::Dashboard"
701 universal_taggable = object()
702 global_resource = True
703
704 source_mapping = {
705 "describe": DescribeWithResourceTags,
706 }
707
708
709@resources.register("destination")
710class Destination(QueryResourceManager):
711 class resource_type(TypeInfo):
712 service = "logs"
713 arn = "arn"
714 arn_separator = ":"
715 arn_type = "destination"
716 cfn_type = "AWS::Logs::Destination"
717 date = "creationTime"
718 enum_spec = ('describe_destinations', 'destinations', None)
719 id = name = "destinationName"
720 universal_taggable = object()
721
722 retry = staticmethod(get_retry(('ServiceUnavailableException', 'OperationAbortedException')))
723
724 source_mapping = {
725 "describe": DescribeWithResourceTags,
726 }
727
728
729@Destination.filter_registry.register('cross-account')
730class DestinationCrossAccount(CrossAccountAccessFilter):
731
732 permissions = ('logs:DescribeDestinations',)
733 policy_attribute = 'accessPolicy'
734
735
736@Destination.action_registry.register('delete')
737class DestinationDelete(BaseAction):
738 """Action to delete a destination
739
740 :example:
741
742 .. code-block:: yaml
743
744 policies:
745 - name: delete-destination
746 resource: aws.destination
747 filters:
748 - type: cross-account
749 actions:
750 - delete
751 """
752 schema = type_schema('delete')
753
754 permissions = ('logs:DeleteDestination',)
755
756 def process(self, resources):
757 client = local_session(self.manager.session_factory).client('logs')
758 for r in resources:
759 self.manager.retry(
760 client.delete_destination,
761 ignore_err_codes=('ResourceNotFoundException',),
762 destinationName=r['destinationName'],
763 )
764
765
766@resources.register("delivery-destination")
767class DeliveryDestination(QueryResourceManager):
768 class resource_type(TypeInfo):
769 service = "logs"
770 enum_spec = ('describe_delivery_destinations', 'deliveryDestinations', None)
771 arn_type = "delivery-destination"
772 arn_separator = ":"
773 arn = "arn"
774 id = name = "name"
775 cfn_type = "AWS::Logs::DeliveryDestination"
776 universal_taggable = object()
777
778 retry = staticmethod(get_retry(
779 ('ConflictException', 'ServiceUnavailableException', 'ThrottlingException',)
780 ))
781 source_mapping = {
782 "describe": DescribeWithResourceTags,
783 }
784
785
786@DeliveryDestination.filter_registry.register('cross-account')
787class DeliveryDestinationCrossAccount(CrossAccountAccessFilter):
788
789 policy_attribute = 'c7n:Policy'
790 permissions = ('logs:GetDeliveryDestinationPolicy',)
791
792 def process(self, resources, event=None):
793 client = local_session(self.manager.session_factory).client('logs')
794
795 for r in resources:
796 resp = self.manager.retry(
797 client.get_delivery_destination_policy,
798 deliveryDestinationName=r['name'],
799 ignore_err_codes=('ResourceNotFoundException',)
800 )
801 r[self.policy_attribute] = resp['policy'].get('deliveryDestinationPolicy', {})
802 return super().process(resources)
803
804
805@DeliveryDestination.action_registry.register('delete')
806class DeliveryDestinationDelete(BaseAction):
807 """Action to delete a delivery destination
808
809 :example:
810
811 .. code-block:: yaml
812
813 policies:
814 - name: delete-delivery-destination
815 resource: aws.delivery-destination
816 filters:
817 - type: value
818 key: deliveryDestinationType
819 value: S3
820 actions:
821 - delete
822 """
823 schema = type_schema('delete')
824
825 permissions = ('logs:DeleteDeliveryDestination',)
826
827 def process(self, resources):
828 client = local_session(self.manager.session_factory).client('logs')
829 for r in resources:
830 self.manager.retry(
831 client.delete_delivery_destination,
832 ignore_err_codes=('ResourceNotFoundException',),
833 name=r['name'],
834 )
835
836
837@resources.register('cloudwatch-synthetics')
838class SyntheticsCanary(QueryResourceManager):
839 """AWS CloudWatch Synthetics Canary
840
841 Example:
842 .. code-block:: yaml
843
844 policies:
845 - name: stop-failed-canaries
846 resource: aws.cloudwatch-synthetics
847 filters:
848 - State.CurrentStatus.State: FAILED
849 actions:
850 - type: delete
851 """
852
853 class resource_type(TypeInfo):
854 service = 'synthetics'
855 id = 'Id'
856 name = 'Name'
857 date = 'LastModified'
858 arn_type = 'canary'
859 dimension = 'CanaryName'
860 cfn_type = 'AWS::Synthetics::Canary'
861 enum_spec = ('describe_canaries', 'Canaries', None)
862 universal_taggable = object()
863
864 def augment(self, resources):
865 for r in resources:
866 # AWS returns tags as a dict { "Key": "Value" }
867 # Custodian expects [{"Key": k, "Value": v}, ...]
868 r["Tags"] = [{"Key": k, "Value": v} for k, v in r["Tags"].items()]
869
870 return resources
871
872
873@SyntheticsCanary.action_registry.register('start')
874class StartCanary(BaseAction):
875 schema = type_schema('start')
876
877 permissions = ('synthetics:StartCanary',)
878
879 def process(self, resources):
880 client = local_session(self.manager.session_factory).client('synthetics')
881 for r in resources:
882 client.start_canary(Name=r['Name'])
883
884
885@SyntheticsCanary.action_registry.register('stop')
886class StopCanary(BaseAction):
887 schema = type_schema('stop')
888
889 permissions = ('synthetics:StopCanary',)
890
891 def process(self, resources):
892 """Stop all running resources"""
893 client = local_session(self.manager.session_factory).client('synthetics')
894 for r in resources:
895 client.stop_canary(Name=r['Name'])
896
897
898@SyntheticsCanary.action_registry.register('delete')
899class DeleteCanary(BaseAction):
900 schema = type_schema('delete')
901
902 permissions = ('synthetics:DeleteCanary',)
903
904 def process(self, resources):
905 """Delete resources"""
906 client = local_session(self.manager.session_factory).client('synthetics')
907 for r in resources:
908 client.delete_canary(Name=r['Name'])