-
Notifications
You must be signed in to change notification settings - Fork 601
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add rule E3505 to validate lambda,sqs timeouts
- Loading branch information
Showing
2 changed files
with
253 additions
and
0 deletions.
There are no files selected for viewing
96 changes: 96 additions & 0 deletions
96
src/cfnlint/rules/resources/lmbd/EventSourceMappingToSqsTimeout.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
""" | ||
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
SPDX-License-Identifier: MIT-0 | ||
""" | ||
|
||
from __future__ import annotations | ||
|
||
from collections import deque | ||
from typing import Any | ||
|
||
from cfnlint.jsonschema import ValidationError, ValidationResult, Validator | ||
from cfnlint.rules.helpers import get_value_from_path | ||
from cfnlint.rules.jsonschema.CfnLintKeyword import CfnLintKeyword | ||
|
||
|
||
class EventSourceMappingToSqsTimeout(CfnLintKeyword): | ||
|
||
id = "E3505" | ||
shortdesc = ( | ||
"Validate SQS 'VisibilityTimeout' is greater than a function's 'Timeout'" | ||
) | ||
description = ( | ||
"When attaching a Lambda function to a SQS queue to a Lambda function the " | ||
"SQS 'VisibilityTimeout' has to be greater than or equal to " | ||
" the lambda functions's 'Timeout'" | ||
) | ||
source_url = "https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-visibility-timeout.html" | ||
tags = ["resources", "lambda", "sqs"] | ||
|
||
def __init__(self): | ||
"""Init""" | ||
super().__init__(["Resources/AWS::Lambda::Function/Properties/Timeout"]) | ||
|
||
def validate( | ||
self, validator: Validator, keywords: Any, instance: Any, schema: dict[str, Any] | ||
) -> ValidationResult: | ||
|
||
if validator.is_type(instance, "string"): | ||
try: | ||
instance = int(instance) | ||
except: # noqa: E722 | ||
return | ||
|
||
if not validator.is_type(instance, "integer"): | ||
return | ||
|
||
if validator.cfn.graph is None: # pragma: no cover | ||
return # pragma: no cover | ||
|
||
if not len(validator.context.path.path) >= 2: | ||
return | ||
|
||
resource_name = validator.context.path.path[1] | ||
for child_1, _ in validator.cfn.graph.graph.in_edges(resource_name): | ||
if child_1 not in validator.context.resources: | ||
continue | ||
|
||
if ( | ||
validator.context.resources[child_1].type | ||
== "AWS::Lambda::EventSourceMapping" | ||
): | ||
for _, child_2 in validator.cfn.graph.graph.out_edges(child_1): | ||
if child_2 not in validator.context.resources: | ||
continue | ||
if validator.context.resources[child_2].type == "AWS::SQS::Queue": | ||
for visibility_timeout, _ in get_value_from_path( | ||
validator, | ||
validator.cfn.template, | ||
deque( | ||
[ | ||
"Resources", | ||
child_2, | ||
"Properties", | ||
"VisibilityTimeout", | ||
] | ||
), | ||
): | ||
if validator.is_type(visibility_timeout, "string"): | ||
try: | ||
visibility_timeout = int(visibility_timeout) | ||
except: # noqa: E722 | ||
continue | ||
|
||
if not validator.is_type(visibility_timeout, "integer"): | ||
continue | ||
|
||
if visibility_timeout < instance: | ||
yield ValidationError( | ||
message=( | ||
f"Queue visibility timeout " | ||
f"({visibility_timeout!r}) " | ||
"is less than Function timeout " | ||
f"({instance!r}) seconds" | ||
), | ||
rule=self, | ||
) |
157 changes: 157 additions & 0 deletions
157
test/unit/rules/resources/lmbd/test_event_source_mapping_to_sqs_timeout.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
""" | ||
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
SPDX-License-Identifier: MIT-0 | ||
""" | ||
|
||
from collections import deque | ||
|
||
import jsonpatch | ||
import pytest | ||
|
||
from cfnlint.jsonschema import ValidationError | ||
from cfnlint.rules.resources.lmbd.EventSourceMappingToSqsTimeout import ( | ||
EventSourceMappingToSqsTimeout, | ||
) | ||
|
||
|
||
@pytest.fixture(scope="module") | ||
def rule(): | ||
rule = EventSourceMappingToSqsTimeout() | ||
yield rule | ||
|
||
|
||
_template = { | ||
"Parameters": { | ||
"BatchSize": {"Type": "String"}, | ||
}, | ||
"Resources": { | ||
"MyFifoQueue": { | ||
"Type": "AWS::SQS::Queue", | ||
"Properties": { | ||
"VisibilityTimeout": 300, | ||
"MessageRetentionPeriod": 7200, | ||
}, | ||
}, | ||
"SQSBatch": { | ||
"Type": "AWS::Lambda::EventSourceMapping", | ||
"Properties": { | ||
"BatchSize": {"Ref": "BatchSize"}, | ||
"Enabled": True, | ||
"EventSourceArn": {"Fn::GetAtt": "MyFifoQueue.Arn"}, | ||
"FunctionName": {"Ref": "Lambda"}, | ||
}, | ||
}, | ||
"Lambda": { | ||
"Type": "AWS::Lambda::Function", | ||
"Properties": {"Role": {"Fn::GetAtt": "Role.Arn"}}, | ||
}, | ||
"CustomResource": { | ||
"Type": "AWS::CloudFormation::CustomResource", | ||
"Properties": {"Key": {"Fn::GetAtt": "Lambda.Arn"}}, | ||
}, | ||
}, | ||
"Outputs": { | ||
"LambdaArn": {"Value": {"Fn::GetAtt": "Lambda.Arn"}}, | ||
"SourceMapping": {"Value": {"Ref": "SQSBatch"}}, | ||
}, | ||
} | ||
|
||
|
||
@pytest.mark.parametrize( | ||
"instance,template,path,expected", | ||
[ | ||
( | ||
"100", | ||
_template, | ||
{"path": deque(["Resources", "Lambda", "Properties", "Timeout"])}, | ||
[], | ||
), | ||
( | ||
"a", | ||
_template, | ||
{"path": deque(["Resources", "Lambda", "Properties", "Timeout"])}, | ||
[], | ||
), | ||
( | ||
{"Ref": "AWS::Region"}, | ||
_template, | ||
{"path": deque(["Resources", "Lambda", "Properties", "Timeout"])}, | ||
[], | ||
), | ||
( | ||
"100", | ||
jsonpatch.apply_patch( | ||
_template, | ||
[ | ||
{ | ||
"op": "add", | ||
"path": ( | ||
"/Resources/MyFifoQueue/" "Properties/VisibilityTimeout" | ||
), | ||
"value": "300", | ||
}, | ||
], | ||
), | ||
{"path": deque(["Resources", "Lambda", "Properties", "Timeout"])}, | ||
[], | ||
), | ||
( | ||
"600", | ||
jsonpatch.apply_patch( | ||
_template, | ||
[ | ||
{ | ||
"op": "add", | ||
"path": ( | ||
"/Resources/MyFifoQueue/" "Properties/VisibilityTimeout" | ||
), | ||
"value": {"Ref": "AWS::Region"}, | ||
}, | ||
], | ||
), | ||
{"path": deque(["Resources", "Lambda", "Properties", "Timeout"])}, | ||
[], | ||
), | ||
( | ||
"600", | ||
jsonpatch.apply_patch( | ||
_template, | ||
[ | ||
{ | ||
"op": "add", | ||
"path": ( | ||
"/Resources/MyFifoQueue/" "Properties/VisibilityTimeout" | ||
), | ||
"value": "a", | ||
}, | ||
], | ||
), | ||
{"path": deque(["Resources", "Lambda", "Properties", "Timeout"])}, | ||
[], | ||
), | ||
( | ||
"600", | ||
_template, | ||
{"path": deque(["Resources"])}, | ||
[], | ||
), | ||
( | ||
"600", | ||
_template, | ||
{"path": deque(["Resources", "Lambda", "Properties", "Timeout"])}, | ||
[ | ||
ValidationError( | ||
( | ||
"Queue visibility timeout (300) is less " | ||
"than Function timeout (600) seconds" | ||
), | ||
rule=EventSourceMappingToSqsTimeout(), | ||
) | ||
], | ||
), | ||
], | ||
indirect=["template", "path"], | ||
) | ||
def test_lambda_runtime(instance, template, path, expected, rule, validator): | ||
errs = list(rule.validate(validator, "", instance, {})) | ||
assert errs == expected, f"Expected {expected} got {errs}" |