Skip to content

Commit d2ab095

Browse files
par6nrubenfonsecaleandrodamascena
authored
feat(data_classes): Add CloudWatchAlarmEvent data class (aws-powertools#3868)
* feat(data_classes): initialise cloud_watch_alarm_event data class * fix(data_classes): add CloudWatchAlarmEvent to dataclasses index * fix(data_classes): add the satellite classes to index * fix(data_classes): address mypy issues * docs(data_classes): add documentation on CloudWatchAlarmEvent * fix(data_classes): add 'expression' and 'label' fields to CloudWatchAlarmMetric * fix(data_classes): change accountId to `123456789012` in cloudWatchAlarmEvent sample * fix(data_classes): add a new `reason_data_decoded` property to CloudWatchAlarmState This will allow the customers to easily access the raw 'reasonData' field, as well as accessing parsed data using the new property. * fix(data_classes): use Literal instead of Enum for the property `value` in CloudWatchAlarmState * improv(data_classes): introduce CloudWatchAlarmData data class that contains the alarm data * improv(data_classes): change source property return type to Literal * docs(data_classes): update the example for CloudWatch Alarm State Change Action * docs(data_classes): add a working example under `examples/event_sources/` * improv(data_classes): use `cached_property` decorator for `reason_data_decoded` * docs(data_classes): reformat table in data_classes * docs(data_classes): fix cloudwatch_alarm_event example typing issue * docs(data_classes): replace example code with reference to the example file for CW alarm state change action * feat(data_classes): add `actions_suppressed_by` and `actions_suppressed_reason` properties to CloudWatchAlarmState * Refactoring code * Refactoring code --------- Co-authored-by: Ruben Fonseca <[email protected]> Co-authored-by: Leandro Damascena <[email protected]>
1 parent fa235c0 commit d2ab095

File tree

7 files changed

+514
-33
lines changed

7 files changed

+514
-33
lines changed

aws_lambda_powertools/utilities/data_classes/__init__.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@
77
from .appsync_resolver_event import AppSyncResolverEvent
88
from .aws_config_rule_event import AWSConfigRuleEvent
99
from .bedrock_agent_event import BedrockAgentEvent
10+
from .cloud_watch_alarm_event import (
11+
CloudWatchAlarmConfiguration,
12+
CloudWatchAlarmData,
13+
CloudWatchAlarmEvent,
14+
CloudWatchAlarmMetric,
15+
CloudWatchAlarmMetricStat,
16+
CloudWatchAlarmState,
17+
)
1018
from .cloud_watch_custom_widget_event import CloudWatchDashboardCustomWidgetEvent
1119
from .cloud_watch_logs_event import CloudWatchLogsEvent
1220
from .code_pipeline_job_event import CodePipelineJobEvent
@@ -42,6 +50,12 @@
4250
"AppSyncResolverEvent",
4351
"ALBEvent",
4452
"BedrockAgentEvent",
53+
"CloudWatchAlarmData",
54+
"CloudWatchAlarmEvent",
55+
"CloudWatchAlarmMetric",
56+
"CloudWatchAlarmState",
57+
"CloudWatchAlarmConfiguration",
58+
"CloudWatchAlarmMetricStat",
4559
"CloudWatchDashboardCustomWidgetEvent",
4660
"CloudWatchLogsEvent",
4761
"CodePipelineJobEvent",
Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
from __future__ import annotations
2+
3+
from functools import cached_property
4+
from typing import Any, Dict, List, Literal, Optional
5+
6+
from aws_lambda_powertools.utilities.data_classes.common import DictWrapper
7+
8+
9+
class CloudWatchAlarmState(DictWrapper):
10+
@property
11+
def value(self) -> Literal["OK", "ALARM", "INSUFFICIENT_DATA"]:
12+
"""
13+
Overall state of the alarm.
14+
"""
15+
return self["value"]
16+
17+
@property
18+
def reason(self) -> str:
19+
"""
20+
Reason why alarm was changed to this state.
21+
"""
22+
return self["reason"]
23+
24+
@property
25+
def reason_data(self) -> str:
26+
"""
27+
Additional data to back up the reason, usually contains the evaluated data points,
28+
the calculated threshold and timestamps.
29+
"""
30+
return self["reasonData"]
31+
32+
@cached_property
33+
def reason_data_decoded(self) -> Optional[Any]:
34+
"""
35+
Deserialized version of reason_data.
36+
"""
37+
38+
return self._json_deserializer(self.reason_data) if self.reason_data else None
39+
40+
@property
41+
def actions_suppressed_by(self) -> Optional[Literal["Alarm", "ExtensionPeriod", "WaitPeriod"]]:
42+
"""
43+
Describes why the actions when the value is `ALARM` are suppressed in a composite
44+
alarm.
45+
"""
46+
return self.get("actionsSuppressedBy", None)
47+
48+
@property
49+
def actions_suppressed_reason(self) -> Optional[str]:
50+
"""
51+
Captures the reason for action suppression.
52+
"""
53+
return self.get("actionsSuppressedReason", None)
54+
55+
@property
56+
def timestamp(self) -> str:
57+
"""
58+
Timestamp of this state change in ISO-8601 format.
59+
"""
60+
return self["timestamp"]
61+
62+
63+
class CloudWatchAlarmMetric(DictWrapper):
64+
@property
65+
def metric_id(self) -> str:
66+
"""
67+
Unique ID of the alarm metric.
68+
"""
69+
return self["id"]
70+
71+
@property
72+
def expression(self) -> Optional[str]:
73+
"""
74+
Optional expression of the alarm metric.
75+
"""
76+
return self.get("expression", None)
77+
78+
@property
79+
def label(self) -> Optional[str]:
80+
"""
81+
Optional label of the alarm metric.
82+
"""
83+
return self.get("label", None)
84+
85+
@property
86+
def return_data(self) -> bool:
87+
"""
88+
Whether this metric data is used to determine the state of the alarm or not.
89+
"""
90+
return self["returnData"]
91+
92+
@property
93+
def metric_stat(self) -> CloudWatchAlarmMetricStat:
94+
return CloudWatchAlarmMetricStat(self["metricStat"])
95+
96+
97+
class CloudWatchAlarmMetricStat(DictWrapper):
98+
@property
99+
def period(self) -> Optional[int]:
100+
"""
101+
Metric evaluation period, in seconds.
102+
"""
103+
return self.get("period", None)
104+
105+
@property
106+
def stat(self) -> Optional[str]:
107+
"""
108+
Statistical aggregation of metric points, e.g. Average, SampleCount, etc.
109+
"""
110+
return self.get("stat", None)
111+
112+
@property
113+
def unit(self) -> Optional[str]:
114+
"""
115+
Unit for metric.
116+
"""
117+
return self.get("unit", None)
118+
119+
@property
120+
def metric(self) -> Optional[Dict]:
121+
"""
122+
Metric details
123+
"""
124+
return self.get("metric", {})
125+
126+
127+
class CloudWatchAlarmData(DictWrapper):
128+
@property
129+
def alarm_name(self) -> str:
130+
"""
131+
Alarm name.
132+
"""
133+
return self["alarmName"]
134+
135+
@property
136+
def state(self) -> CloudWatchAlarmState:
137+
"""
138+
The current state of the Alarm.
139+
"""
140+
return CloudWatchAlarmState(self["state"])
141+
142+
@property
143+
def previous_state(self) -> CloudWatchAlarmState:
144+
"""
145+
The previous state of the Alarm.
146+
"""
147+
return CloudWatchAlarmState(self["previousState"])
148+
149+
@property
150+
def configuration(self) -> CloudWatchAlarmConfiguration:
151+
"""
152+
The configuration of the Alarm.
153+
"""
154+
return CloudWatchAlarmConfiguration(self["configuration"])
155+
156+
157+
class CloudWatchAlarmConfiguration(DictWrapper):
158+
@property
159+
def description(self) -> Optional[str]:
160+
"""
161+
Optional description for the Alarm.
162+
"""
163+
return self.get("description", None)
164+
165+
@property
166+
def alarm_rule(self) -> Optional[str]:
167+
"""
168+
Optional description for the Alarm rule in case of composite alarm.
169+
"""
170+
return self.get("alarmRule", None)
171+
172+
@property
173+
def alarm_actions_suppressor(self) -> Optional[str]:
174+
"""
175+
Optional action suppression for the Alarm rule in case of composite alarm.
176+
"""
177+
return self.get("actionsSuppressor", None)
178+
179+
@property
180+
def alarm_actions_suppressor_wait_period(self) -> Optional[str]:
181+
"""
182+
Optional action suppression wait period for the Alarm rule in case of composite alarm.
183+
"""
184+
return self.get("actionsSuppressorWaitPeriod", None)
185+
186+
@property
187+
def alarm_actions_suppressor_extension_period(self) -> Optional[str]:
188+
"""
189+
Optional action suppression extension period for the Alarm rule in case of composite alarm.
190+
"""
191+
return self.get("actionsSuppressorExtensionPeriod", None)
192+
193+
@property
194+
def metrics(self) -> Optional[List[CloudWatchAlarmMetric]]:
195+
"""
196+
The metrics evaluated for the Alarm.
197+
"""
198+
metrics = self.get("metrics")
199+
return [CloudWatchAlarmMetric(i) for i in metrics] if metrics else None
200+
201+
202+
class CloudWatchAlarmEvent(DictWrapper):
203+
@property
204+
def source(self) -> Literal["aws.cloudwatch"]:
205+
"""
206+
Source of the triggered event.
207+
"""
208+
return self["source"]
209+
210+
@property
211+
def alarm_arn(self) -> str:
212+
"""
213+
The ARN of the CloudWatch Alarm.
214+
"""
215+
return self["alarmArn"]
216+
217+
@property
218+
def region(self) -> str:
219+
"""
220+
The AWS region in which the Alarm is active.
221+
"""
222+
return self["region"]
223+
224+
@property
225+
def source_account_id(self) -> str:
226+
"""
227+
The AWS Account ID that the Alarm is deployed to.
228+
"""
229+
return self["accountId"]
230+
231+
@property
232+
def timestamp(self) -> str:
233+
"""
234+
Alarm state change event timestamp in ISO-8601 format.
235+
"""
236+
return self["time"]
237+
238+
@property
239+
def alarm_data(self) -> CloudWatchAlarmData:
240+
"""
241+
Contains basic data about the Alarm and its current and previous states.
242+
"""
243+
return CloudWatchAlarmData(self["alarmData"])

docs/utilities/data_classes.md

Lines changed: 45 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -74,39 +74,40 @@ Log Data Event for Troubleshooting
7474

7575
## Supported event sources
7676

77-
| Event Source | Data_class |
78-
|---------------------------------------------------------------------------|----------------------------------------------------|
79-
| [Active MQ](#active-mq) | `ActiveMQEvent` |
80-
| [API Gateway Authorizer](#api-gateway-authorizer) | `APIGatewayAuthorizerRequestEvent` |
81-
| [API Gateway Authorizer V2](#api-gateway-authorizer-v2) | `APIGatewayAuthorizerEventV2` |
82-
| [API Gateway Proxy](#api-gateway-proxy) | `APIGatewayProxyEvent` |
83-
| [API Gateway Proxy V2](#api-gateway-proxy-v2) | `APIGatewayProxyEventV2` |
84-
| [Application Load Balancer](#application-load-balancer) | `ALBEvent` |
85-
| [AppSync Authorizer](#appsync-authorizer) | `AppSyncAuthorizerEvent` |
86-
| [AppSync Resolver](#appsync-resolver) | `AppSyncResolverEvent` |
87-
| [AWS Config Rule](#aws-config-rule) | `AWSConfigRuleEvent` |
88-
| [Bedrock Agent](#bedrock-agent) | `BedrockAgent` |
89-
| [CloudWatch Dashboard Custom Widget](#cloudwatch-dashboard-custom-widget) | `CloudWatchDashboardCustomWidgetEvent` |
90-
| [CloudWatch Logs](#cloudwatch-logs) | `CloudWatchLogsEvent` |
91-
| [CodePipeline Job Event](#codepipeline-job) | `CodePipelineJobEvent` |
92-
| [Cognito User Pool](#cognito-user-pool) | Multiple available under `cognito_user_pool_event` |
93-
| [Connect Contact Flow](#connect-contact-flow) | `ConnectContactFlowEvent` |
94-
| [DynamoDB streams](#dynamodb-streams) | `DynamoDBStreamEvent`, `DynamoDBRecordEventName` |
95-
| [EventBridge](#eventbridge) | `EventBridgeEvent` |
96-
| [Kafka](#kafka) | `KafkaEvent` |
97-
| [Kinesis Data Stream](#kinesis-streams) | `KinesisStreamEvent` |
98-
| [Kinesis Firehose Delivery Stream](#kinesis-firehose-delivery-stream) | `KinesisFirehoseEvent` |
99-
| [Lambda Function URL](#lambda-function-url) | `LambdaFunctionUrlEvent` |
100-
| [Rabbit MQ](#rabbit-mq) | `RabbitMQEvent` |
101-
| [S3](#s3) | `S3Event` |
102-
| [S3 Batch Operations](#s3-batch-operations) | `S3BatchOperationEvent` |
103-
| [S3 Object Lambda](#s3-object-lambda) | `S3ObjectLambdaEvent` |
104-
| [S3 EventBridge Notification](#s3-eventbridge-notification) | `S3EventBridgeNotificationEvent` |
105-
| [SES](#ses) | `SESEvent` |
106-
| [SNS](#sns) | `SNSEvent` |
107-
| [SQS](#sqs) | `SQSEvent` |
108-
| [VPC Lattice V2](#vpc-lattice-v2) | `VPCLatticeV2Event` |
109-
| [VPC Lattice V1](#vpc-lattice-v1) | `VPCLatticeEvent` |
77+
| Event Source | Data_class |
78+
|-------------------------------------------------------------------------------|----------------------------------------------------|
79+
| [Active MQ](#active-mq) | `ActiveMQEvent` |
80+
| [API Gateway Authorizer](#api-gateway-authorizer) | `APIGatewayAuthorizerRequestEvent` |
81+
| [API Gateway Authorizer V2](#api-gateway-authorizer-v2) | `APIGatewayAuthorizerEventV2` |
82+
| [API Gateway Proxy](#api-gateway-proxy) | `APIGatewayProxyEvent` |
83+
| [API Gateway Proxy V2](#api-gateway-proxy-v2) | `APIGatewayProxyEventV2` |
84+
| [Application Load Balancer](#application-load-balancer) | `ALBEvent` |
85+
| [AppSync Authorizer](#appsync-authorizer) | `AppSyncAuthorizerEvent` |
86+
| [AppSync Resolver](#appsync-resolver) | `AppSyncResolverEvent` |
87+
| [AWS Config Rule](#aws-config-rule) | `AWSConfigRuleEvent` |
88+
| [Bedrock Agent](#bedrock-agent) | `BedrockAgent` |
89+
| [CloudWatch Alarm State Change Action](#cloudwatch-alarm-state-change-action) | `CloudWatchAlarmEvent` |
90+
| [CloudWatch Dashboard Custom Widget](#cloudwatch-dashboard-custom-widget) | `CloudWatchDashboardCustomWidgetEvent` |
91+
| [CloudWatch Logs](#cloudwatch-logs) | `CloudWatchLogsEvent` |
92+
| [CodePipeline Job Event](#codepipeline-job) | `CodePipelineJobEvent` |
93+
| [Cognito User Pool](#cognito-user-pool) | Multiple available under `cognito_user_pool_event` |
94+
| [Connect Contact Flow](#connect-contact-flow) | `ConnectContactFlowEvent` |
95+
| [DynamoDB streams](#dynamodb-streams) | `DynamoDBStreamEvent`, `DynamoDBRecordEventName` |
96+
| [EventBridge](#eventbridge) | `EventBridgeEvent` |
97+
| [Kafka](#kafka) | `KafkaEvent` |
98+
| [Kinesis Data Stream](#kinesis-streams) | `KinesisStreamEvent` |
99+
| [Kinesis Firehose Delivery Stream](#kinesis-firehose-delivery-stream) | `KinesisFirehoseEvent` |
100+
| [Lambda Function URL](#lambda-function-url) | `LambdaFunctionUrlEvent` |
101+
| [Rabbit MQ](#rabbit-mq) | `RabbitMQEvent` |
102+
| [S3](#s3) | `S3Event` |
103+
| [S3 Batch Operations](#s3-batch-operations) | `S3BatchOperationEvent` |
104+
| [S3 Object Lambda](#s3-object-lambda) | `S3ObjectLambdaEvent` |
105+
| [S3 EventBridge Notification](#s3-eventbridge-notification) | `S3EventBridgeNotificationEvent` |
106+
| [SES](#ses) | `SESEvent` |
107+
| [SNS](#sns) | `SNSEvent` |
108+
| [SQS](#sqs) | `SQSEvent` |
109+
| [VPC Lattice V2](#vpc-lattice-v2) | `VPCLatticeV2Event` |
110+
| [VPC Lattice V1](#vpc-lattice-v1) | `VPCLatticeEvent` |
110111

111112
???+ info
112113
The examples provided below are far from exhaustive - the data classes themselves are designed to provide a form of
@@ -528,6 +529,17 @@ In this example, we also use the new Logger `correlation_id` and built-in `corre
528529
return { "markdown": f"# {echo}" }
529530
```
530531

532+
### CloudWatch Alarm State Change Action
533+
534+
[CloudWatch supports Lambda as an alarm state change action](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/AlarmThatSendsEmail.html#alarms-and-actions){target="_blank"}.
535+
You can use the `CloudWathAlarmEvent` data class to access the fields containing such data as alarm information, current state, and previous state.
536+
537+
=== "app.py"
538+
539+
```python hl_lines="2 8"
540+
--8<-- "examples/event_sources/src/cloudwatch_alarm_event.py"
541+
```
542+
531543
### CloudWatch Logs
532544

533545
CloudWatch Logs events by default are compressed and base64 encoded. You can use the helper function provided to decode,
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from aws_lambda_powertools import Logger
2+
from aws_lambda_powertools.utilities.data_classes import CloudWatchAlarmEvent, event_source
3+
from aws_lambda_powertools.utilities.typing import LambdaContext
4+
5+
logger = Logger()
6+
7+
8+
@event_source(data_class=CloudWatchAlarmEvent)
9+
def lambda_handler(event: CloudWatchAlarmEvent, context: LambdaContext) -> dict:
10+
logger.info(f"Alarm {event.alarm_data.alarm_name} state is {event.alarm_data.state.value}")
11+
12+
# You can now work with event. For example, you can enrich the received data, and
13+
# decide on how you want to route the alarm.
14+
15+
return {
16+
"name": event.alarm_data.alarm_name,
17+
"arn": event.alarm_arn,
18+
"urgent": "Priority: P1" in (event.alarm_data.configuration.description or ""),
19+
}

0 commit comments

Comments
 (0)