async-lambda-unstable 0.6.13__tar.gz → 0.6.14__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {async_lambda_unstable-0.6.13 → async_lambda_unstable-0.6.14}/PKG-INFO +22 -1
- {async_lambda_unstable-0.6.13 → async_lambda_unstable-0.6.14}/README.md +21 -0
- {async_lambda_unstable-0.6.13 → async_lambda_unstable-0.6.14}/async_lambda/__init__.py +1 -1
- {async_lambda_unstable-0.6.13 → async_lambda_unstable-0.6.14}/async_lambda/build_config.py +50 -0
- {async_lambda_unstable-0.6.13 → async_lambda_unstable-0.6.14}/async_lambda/controller.py +102 -1
- {async_lambda_unstable-0.6.13 → async_lambda_unstable-0.6.14}/.gitignore +0 -0
- {async_lambda_unstable-0.6.13 → async_lambda_unstable-0.6.14}/async_lambda/cli.py +0 -0
- {async_lambda_unstable-0.6.13 → async_lambda_unstable-0.6.14}/async_lambda/client.py +0 -0
- {async_lambda_unstable-0.6.13 → async_lambda_unstable-0.6.14}/async_lambda/config.py +0 -0
- {async_lambda_unstable-0.6.13 → async_lambda_unstable-0.6.14}/async_lambda/defer.py +0 -0
- {async_lambda_unstable-0.6.13 → async_lambda_unstable-0.6.14}/async_lambda/env.py +0 -0
- {async_lambda_unstable-0.6.13 → async_lambda_unstable-0.6.14}/async_lambda/middleware.py +0 -0
- {async_lambda_unstable-0.6.13 → async_lambda_unstable-0.6.14}/async_lambda/models/__init__.py +0 -0
- {async_lambda_unstable-0.6.13 → async_lambda_unstable-0.6.14}/async_lambda/models/api_response.py +0 -0
- {async_lambda_unstable-0.6.13 → async_lambda_unstable-0.6.14}/async_lambda/models/case_insensitive_dict.py +0 -0
- {async_lambda_unstable-0.6.13 → async_lambda_unstable-0.6.14}/async_lambda/models/events/__init__.py +0 -0
- {async_lambda_unstable-0.6.13 → async_lambda_unstable-0.6.14}/async_lambda/models/events/api_event.py +0 -0
- {async_lambda_unstable-0.6.13 → async_lambda_unstable-0.6.14}/async_lambda/models/events/base_event.py +0 -0
- {async_lambda_unstable-0.6.13 → async_lambda_unstable-0.6.14}/async_lambda/models/events/dynamodb_event.py +0 -0
- {async_lambda_unstable-0.6.13 → async_lambda_unstable-0.6.14}/async_lambda/models/events/managed_sqs_batch_event.py +0 -0
- {async_lambda_unstable-0.6.13 → async_lambda_unstable-0.6.14}/async_lambda/models/events/managed_sqs_event.py +0 -0
- {async_lambda_unstable-0.6.13 → async_lambda_unstable-0.6.14}/async_lambda/models/events/scheduled_event.py +0 -0
- {async_lambda_unstable-0.6.13 → async_lambda_unstable-0.6.14}/async_lambda/models/events/unmanaged_sqs_event.py +0 -0
- {async_lambda_unstable-0.6.13 → async_lambda_unstable-0.6.14}/async_lambda/models/mock/mock_context.py +0 -0
- {async_lambda_unstable-0.6.13 → async_lambda_unstable-0.6.14}/async_lambda/models/mock/mock_event.py +0 -0
- {async_lambda_unstable-0.6.13 → async_lambda_unstable-0.6.14}/async_lambda/models/task.py +0 -0
- {async_lambda_unstable-0.6.13 → async_lambda_unstable-0.6.14}/async_lambda/payload_encoder.py +0 -0
- {async_lambda_unstable-0.6.13 → async_lambda_unstable-0.6.14}/async_lambda/py.typed +0 -0
- {async_lambda_unstable-0.6.13 → async_lambda_unstable-0.6.14}/async_lambda/util.py +0 -0
- {async_lambda_unstable-0.6.13 → async_lambda_unstable-0.6.14}/pyproject.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: async-lambda-unstable
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.14
|
|
4
4
|
Summary: A framework for creating AWS Lambda Async Workflows. - Unstable Branch
|
|
5
5
|
Author-email: "Nuclei, Inc" <engineering@nuclei.ai>
|
|
6
6
|
Requires-Dist: click>=8.0.0
|
|
@@ -437,6 +437,27 @@ Two caveats:
|
|
|
437
437
|
- A task with task-specific `policies` keeps its own generated role, since the shared role is built from app-wide and stage-wide config only.
|
|
438
438
|
- Every app-wide policy must be either a managed policy ARN or a `{"Statement": [...]}` document. SAM policy templates can't be expressed in a plain IAM role and will fail the build.
|
|
439
439
|
|
|
440
|
+
### Consolidated Queue-Age Alarms
|
|
441
|
+
|
|
442
|
+
`managed_queue_extras` attaches a copy of each extra resource to *every* managed queue, so using it for a CloudWatch alarm costs one stack resource per queue lane.
|
|
443
|
+
|
|
444
|
+
Setting `queue_age_alarm` in the build config emits a small number of grouped alarms instead, each watching `ApproximateAgeOfOldestMessage` across up to ten queues:
|
|
445
|
+
|
|
446
|
+
```json
|
|
447
|
+
{
|
|
448
|
+
"queue_age_alarm": {
|
|
449
|
+
"threshold": 5000,
|
|
450
|
+
"period": 86400,
|
|
451
|
+
"alarm_actions": ["arn:aws:sns:us-east-1:123456789012:alarms"],
|
|
452
|
+
"ok_actions": ["arn:aws:sns:us-east-1:123456789012:alarms"]
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
Only `threshold` is required and `period` defaults to one day. Coverage is every managed queue lane plus the app DLQ. An app with 104 queues gets 11 alarms rather than 104.
|
|
458
|
+
|
|
459
|
+
CloudWatch rejects an alarm carrying more than 10 metrics.
|
|
460
|
+
|
|
440
461
|
## Known Limitations
|
|
441
462
|
|
|
442
463
|
- Not all Lambda configuration options are supported (see code for extension points)
|
|
@@ -425,6 +425,27 @@ Two caveats:
|
|
|
425
425
|
- A task with task-specific `policies` keeps its own generated role, since the shared role is built from app-wide and stage-wide config only.
|
|
426
426
|
- Every app-wide policy must be either a managed policy ARN or a `{"Statement": [...]}` document. SAM policy templates can't be expressed in a plain IAM role and will fail the build.
|
|
427
427
|
|
|
428
|
+
### Consolidated Queue-Age Alarms
|
|
429
|
+
|
|
430
|
+
`managed_queue_extras` attaches a copy of each extra resource to *every* managed queue, so using it for a CloudWatch alarm costs one stack resource per queue lane.
|
|
431
|
+
|
|
432
|
+
Setting `queue_age_alarm` in the build config emits a small number of grouped alarms instead, each watching `ApproximateAgeOfOldestMessage` across up to ten queues:
|
|
433
|
+
|
|
434
|
+
```json
|
|
435
|
+
{
|
|
436
|
+
"queue_age_alarm": {
|
|
437
|
+
"threshold": 5000,
|
|
438
|
+
"period": 86400,
|
|
439
|
+
"alarm_actions": ["arn:aws:sns:us-east-1:123456789012:alarms"],
|
|
440
|
+
"ok_actions": ["arn:aws:sns:us-east-1:123456789012:alarms"]
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
Only `threshold` is required and `period` defaults to one day. Coverage is every managed queue lane plus the app DLQ. An app with 104 queues gets 11 alarms rather than 104.
|
|
446
|
+
|
|
447
|
+
CloudWatch rejects an alarm carrying more than 10 metrics.
|
|
448
|
+
|
|
428
449
|
## Known Limitations
|
|
429
450
|
|
|
430
451
|
- Not all Lambda configuration options are supported (see code for extension points)
|
|
@@ -22,4 +22,4 @@ from .models.events.managed_sqs_event import ManagedSQSEvent as ManagedSQSEvent
|
|
|
22
22
|
from .models.events.scheduled_event import ScheduledEvent as ScheduledEvent
|
|
23
23
|
from .models.events.unmanaged_sqs_event import UnmanagedSQSEvent as UnmanagedSQSEvent
|
|
24
24
|
|
|
25
|
-
__version__ = "0.6.
|
|
25
|
+
__version__ = "0.6.14"
|
|
@@ -14,6 +14,46 @@ def make_default_tags() -> Dict[str, str]:
|
|
|
14
14
|
return {"framework": "async-lambda", "framework-version": __version__}
|
|
15
15
|
|
|
16
16
|
|
|
17
|
+
@dataclass
|
|
18
|
+
class QueueAgeAlarmConfig:
|
|
19
|
+
"""
|
|
20
|
+
Settings for the consolidated queue-age alarms.
|
|
21
|
+
|
|
22
|
+
One alarm covers a group of queues rather than one alarm per queue.
|
|
23
|
+
|
|
24
|
+
Attributes:
|
|
25
|
+
threshold (int): Oldest-message age, in seconds, above which the alarm fires.
|
|
26
|
+
period (int): Seconds of data each datapoint covers.
|
|
27
|
+
alarm_actions (List[str]): ARNs notified when an alarm starts firing.
|
|
28
|
+
ok_actions (List[str]): ARNs notified when an alarm recovers.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
threshold: int
|
|
32
|
+
period: int
|
|
33
|
+
alarm_actions: List[str]
|
|
34
|
+
ok_actions: List[str]
|
|
35
|
+
|
|
36
|
+
@classmethod
|
|
37
|
+
def new(cls, config: dict) -> "QueueAgeAlarmConfig":
|
|
38
|
+
"""
|
|
39
|
+
Creates a new instance from a configuration dictionary.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
config (dict): provided the values of QueueAgeAlarmConfig
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
QueueAgeAlarmConfig: A new instance configured with the provided options.
|
|
46
|
+
"""
|
|
47
|
+
if "threshold" not in config:
|
|
48
|
+
raise ValueError("queue_age_alarm requires a `threshold`.")
|
|
49
|
+
return cls(
|
|
50
|
+
threshold=config["threshold"],
|
|
51
|
+
period=config.get("period", 86400),
|
|
52
|
+
alarm_actions=list(config.get("alarm_actions", list())),
|
|
53
|
+
ok_actions=list(config.get("ok_actions", list())),
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
17
57
|
@dataclass
|
|
18
58
|
class AsyncLambdaBuildConfig:
|
|
19
59
|
"""
|
|
@@ -26,6 +66,7 @@ class AsyncLambdaBuildConfig:
|
|
|
26
66
|
subnet_ids (Set[str]): Set of subnet IDs for VPC configuration.
|
|
27
67
|
security_group_ids (Set[str]): Set of security group IDs for VPC configuration.
|
|
28
68
|
managed_queue_extras (List[dict]): Additional configuration for managed queues.
|
|
69
|
+
queue_age_alarm (Optional[QueueAgeAlarmConfig]): Settings for the consolidated queue-age alarms. No alarms are emitted when this is unset.
|
|
29
70
|
method_settings (List[dict]): API Gateway method settings.
|
|
30
71
|
tags (Dict[str, str]): Tags to assign to the Lambda function.
|
|
31
72
|
logging_config (Dict[str, str]): Logging configuration options.
|
|
@@ -57,6 +98,7 @@ class AsyncLambdaBuildConfig:
|
|
|
57
98
|
certificate_arn: Optional[str] = None
|
|
58
99
|
hosted_zone_id: Optional[str] = None
|
|
59
100
|
auto_create_acm_certificate: Optional[bool] = None
|
|
101
|
+
queue_age_alarm: Optional[QueueAgeAlarmConfig] = None
|
|
60
102
|
|
|
61
103
|
@classmethod
|
|
62
104
|
def new(cls, config: dict) -> "AsyncLambdaBuildConfig":
|
|
@@ -71,6 +113,7 @@ class AsyncLambdaBuildConfig:
|
|
|
71
113
|
- subnet_ids (set or list): Set or list of subnet IDs for VPC configuration.
|
|
72
114
|
- security_group_ids (set or list): Set or list of security group IDs for VPC configuration.
|
|
73
115
|
- managed_queue_extras (list): Additional managed queue configuration.
|
|
116
|
+
- queue_age_alarm (dict): Settings for the consolidated queue-age alarms.
|
|
74
117
|
- method_settings (list): List of method settings for API Gateway.
|
|
75
118
|
- tags (dict): Tags to assign to the Lambda function.
|
|
76
119
|
- logging_config (dict): Logging configuration options.
|
|
@@ -89,6 +132,11 @@ class AsyncLambdaBuildConfig:
|
|
|
89
132
|
subnet_ids=set(config.get("subnet_ids", set())),
|
|
90
133
|
security_group_ids=set(config.get("security_group_ids", set())),
|
|
91
134
|
managed_queue_extras=list(config.get("managed_queue_extras", list())),
|
|
135
|
+
queue_age_alarm=(
|
|
136
|
+
QueueAgeAlarmConfig.new(config["queue_age_alarm"])
|
|
137
|
+
if config.get("queue_age_alarm") is not None
|
|
138
|
+
else None
|
|
139
|
+
),
|
|
92
140
|
method_settings=list(config.get("method_settings", list())),
|
|
93
141
|
tags=config.get("tags", dict()),
|
|
94
142
|
logging_config=config.get("logging_config", dict()),
|
|
@@ -118,6 +166,8 @@ class AsyncLambdaBuildConfig:
|
|
|
118
166
|
self.hosted_zone_id = other.hosted_zone_id
|
|
119
167
|
if other.auto_create_acm_certificate is not None:
|
|
120
168
|
self.auto_create_acm_certificate = other.auto_create_acm_certificate
|
|
169
|
+
if other.queue_age_alarm is not None:
|
|
170
|
+
self.queue_age_alarm = other.queue_age_alarm
|
|
121
171
|
|
|
122
172
|
@property
|
|
123
173
|
def function_properties(self):
|
|
@@ -20,7 +20,11 @@ from typing import (
|
|
|
20
20
|
from uuid import uuid4
|
|
21
21
|
|
|
22
22
|
from . import env
|
|
23
|
-
from .build_config import
|
|
23
|
+
from .build_config import (
|
|
24
|
+
AsyncLambdaBuildConfig,
|
|
25
|
+
QueueAgeAlarmConfig,
|
|
26
|
+
get_build_config_for_stage,
|
|
27
|
+
)
|
|
24
28
|
from .client import get_s3_client, get_scheduler_client, get_sqs_client
|
|
25
29
|
from .config import config
|
|
26
30
|
from .middleware import MET, RT, MiddlewareFunction, MiddlewareRegistration
|
|
@@ -46,6 +50,9 @@ logger = logging.getLogger(__name__)
|
|
|
46
50
|
|
|
47
51
|
_SQS_MAX_DELAY_SECONDS = 900
|
|
48
52
|
|
|
53
|
+
# CloudWatch rejects an alarm carrying more than 10 metrics.
|
|
54
|
+
_MAX_METRICS_PER_ALARM = 10
|
|
55
|
+
|
|
49
56
|
BaseEventT = TypeVar("BaseEventT", bound=BaseEvent)
|
|
50
57
|
APIEventT = TypeVar("APIEventT", bound=APIEvent)
|
|
51
58
|
ManagedSQSEventT = TypeVar("ManagedSQSEventT", bound=ManagedSQSEvent)
|
|
@@ -231,6 +238,92 @@ class AsyncLambdaController:
|
|
|
231
238
|
+ f"{config.name}-*"
|
|
232
239
|
}
|
|
233
240
|
|
|
241
|
+
def _alarmable_queue_logical_ids(self) -> List[str]:
|
|
242
|
+
"""
|
|
243
|
+
Logical IDs of every queue the queue-age alarms should watch.
|
|
244
|
+
|
|
245
|
+
Covers each managed queue lane plus the app DLQ. The delay DLQ is left out
|
|
246
|
+
because it is named deterministically and so can be alarmed on directly.
|
|
247
|
+
"""
|
|
248
|
+
logical_ids = []
|
|
249
|
+
for task in self.tasks.values():
|
|
250
|
+
if task.trigger_type not in MANAGED_SQS_TASK_TYPES:
|
|
251
|
+
continue
|
|
252
|
+
for lane_index in range(task.get_lane_count()):
|
|
253
|
+
logical_ids.append(task.get_managed_queue_logical_id(lane=lane_index))
|
|
254
|
+
logical_ids.append("AsyncLambdaDLQ")
|
|
255
|
+
return logical_ids
|
|
256
|
+
|
|
257
|
+
@staticmethod
|
|
258
|
+
def _build_queue_age_alarms(
|
|
259
|
+
queue_logical_ids: List[str],
|
|
260
|
+
alarm_config: QueueAgeAlarmConfig,
|
|
261
|
+
) -> Dict[str, dict]:
|
|
262
|
+
"""
|
|
263
|
+
Builds alarms watching the oldest message age across every managed queue.
|
|
264
|
+
|
|
265
|
+
Queues are grouped rather than alarmed individually.
|
|
266
|
+
|
|
267
|
+
Each alarm watches `MAX(METRICS())` over its own group. `METRICS()` resolves to just that alarm's
|
|
268
|
+
metrics.
|
|
269
|
+
|
|
270
|
+
Grouping is required because CloudWatch caps an alarm at 10 metrics.
|
|
271
|
+
"""
|
|
272
|
+
alarms = {}
|
|
273
|
+
for alarm_index, group_start in enumerate(
|
|
274
|
+
range(0, len(queue_logical_ids), _MAX_METRICS_PER_ALARM)
|
|
275
|
+
):
|
|
276
|
+
group = queue_logical_ids[
|
|
277
|
+
group_start : group_start + _MAX_METRICS_PER_ALARM
|
|
278
|
+
]
|
|
279
|
+
metrics: List[dict] = [
|
|
280
|
+
{
|
|
281
|
+
"Id": f"q{metric_index}",
|
|
282
|
+
"ReturnData": False,
|
|
283
|
+
"MetricStat": {
|
|
284
|
+
"Metric": {
|
|
285
|
+
"Namespace": "AWS/SQS",
|
|
286
|
+
"MetricName": "ApproximateAgeOfOldestMessage",
|
|
287
|
+
"Dimensions": [
|
|
288
|
+
{
|
|
289
|
+
"Name": "QueueName",
|
|
290
|
+
"Value": {
|
|
291
|
+
"Fn::GetAtt": [queue_logical_id, "QueueName"]
|
|
292
|
+
},
|
|
293
|
+
}
|
|
294
|
+
],
|
|
295
|
+
},
|
|
296
|
+
"Period": alarm_config.period,
|
|
297
|
+
"Stat": "Maximum",
|
|
298
|
+
},
|
|
299
|
+
}
|
|
300
|
+
for metric_index, queue_logical_id in enumerate(group)
|
|
301
|
+
]
|
|
302
|
+
metrics.append(
|
|
303
|
+
{
|
|
304
|
+
"Id": "oldest_message_age",
|
|
305
|
+
"Expression": "MAX(METRICS())",
|
|
306
|
+
"ReturnData": True,
|
|
307
|
+
}
|
|
308
|
+
)
|
|
309
|
+
alarms[f"AsyncLambdaQueueAgeAlarm{alarm_index}"] = {
|
|
310
|
+
"Type": "AWS::CloudWatch::Alarm",
|
|
311
|
+
"Properties": {
|
|
312
|
+
"ActionsEnabled": True,
|
|
313
|
+
"AlarmActions": alarm_config.alarm_actions,
|
|
314
|
+
"OKActions": alarm_config.ok_actions,
|
|
315
|
+
"AlarmDescription": (
|
|
316
|
+
f"Oldest message age across {len(group)} {config.name} queues"
|
|
317
|
+
),
|
|
318
|
+
"ComparisonOperator": "GreaterThanThreshold",
|
|
319
|
+
"EvaluationPeriods": 1,
|
|
320
|
+
"Metrics": metrics,
|
|
321
|
+
"Threshold": alarm_config.threshold,
|
|
322
|
+
"TreatMissingData": "notBreaching",
|
|
323
|
+
},
|
|
324
|
+
}
|
|
325
|
+
return alarms
|
|
326
|
+
|
|
234
327
|
@classmethod
|
|
235
328
|
def _build_shared_function_role(
|
|
236
329
|
cls,
|
|
@@ -744,6 +837,14 @@ class AsyncLambdaController:
|
|
|
744
837
|
self._dlq_extras_replace_references(extra)
|
|
745
838
|
)
|
|
746
839
|
|
|
840
|
+
if build_config.queue_age_alarm is not None:
|
|
841
|
+
template["Resources"].update(
|
|
842
|
+
self._build_queue_age_alarms(
|
|
843
|
+
queue_logical_ids=self._alarmable_queue_logical_ids(),
|
|
844
|
+
alarm_config=build_config.queue_age_alarm,
|
|
845
|
+
)
|
|
846
|
+
)
|
|
847
|
+
|
|
747
848
|
if has_api_tasks:
|
|
748
849
|
properties: dict = {
|
|
749
850
|
"StageName": "prod",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{async_lambda_unstable-0.6.13 → async_lambda_unstable-0.6.14}/async_lambda/models/__init__.py
RENAMED
|
File without changes
|
{async_lambda_unstable-0.6.13 → async_lambda_unstable-0.6.14}/async_lambda/models/api_response.py
RENAMED
|
File without changes
|
|
File without changes
|
{async_lambda_unstable-0.6.13 → async_lambda_unstable-0.6.14}/async_lambda/models/events/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{async_lambda_unstable-0.6.13 → async_lambda_unstable-0.6.14}/async_lambda/models/mock/mock_event.py
RENAMED
|
File without changes
|
|
File without changes
|
{async_lambda_unstable-0.6.13 → async_lambda_unstable-0.6.14}/async_lambda/payload_encoder.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|