Skip to content

Commit d2ab095

Browse files
par6nrubenfonsecaleandrodamascena
authored
feat(data_classes): Add CloudWatchAlarmEvent data class (#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

+14
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",
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

+45-33
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,
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)