Skip to content

Commit e96161f

Browse files
committed
Add ServiceTimeout rule
The ServiceTimeout rule emits a warning if the ServiceTimeout property is not specified for a CloudFormation Custom Resource.
1 parent a1c8868 commit e96161f

File tree

4 files changed

+118
-0
lines changed

4 files changed

+118
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
"""
2+
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
SPDX-License-Identifier: MIT-0
4+
"""
5+
6+
from cfnlint.rules import CloudFormationLintRule, RuleMatch
7+
8+
9+
class ServiceTimeout(CloudFormationLintRule):
10+
"""Check a ServiceTimeout property is specified for custom resources"""
11+
12+
id = "W3046"
13+
shortdesc = "Ensure a service timeout is specified"
14+
description = """
15+
Custom resources should have a ServiceTimeout property specified.
16+
The default service timeout is 60 minutes.
17+
Specify a short timeout to reduce waiting if the custom resource fails
18+
"""
19+
source_url = "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudformation-customresource.html#cfn-cloudformation-customresource-servicetimeout"
20+
tags = [
21+
"resources",
22+
"cloudformation",
23+
"custom resource",
24+
]
25+
26+
def match(self, cfn):
27+
"""Basic Rule Matching"""
28+
29+
matches = []
30+
31+
resources = self._get_custom_resources(cfn)
32+
33+
for resource_name, attributes in resources.items():
34+
properties = attributes.get("Properties", {})
35+
resource_type = attributes.get("Type", None)
36+
37+
if "ServiceTimeout" not in properties:
38+
message = f"Missing ServiceTimeout property in {resource_type}"
39+
matches.append(RuleMatch(["Resources", resource_name], message))
40+
41+
return matches
42+
43+
def _get_custom_resources(self, cfn):
44+
resources = cfn.template.get("Resources", {})
45+
if not isinstance(resources, dict):
46+
return {}
47+
48+
results = {}
49+
for k, v in resources.items():
50+
if isinstance(v, dict):
51+
if (v.get("Type", None) == "AWS::CloudFormation::CustomResource") or (
52+
v.get("Type", None).startswith("Custom::")
53+
):
54+
results[k] = v
55+
56+
return results
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
Resources:
2+
CustomResource:
3+
Type: AWS::CloudFormation::CustomResource
4+
Properties:
5+
ServiceToken: "arn::aws::fake"
6+
7+
CustomResource2:
8+
Type: Custom::CustomResource
9+
Properties:
10+
ServiceToken: "arn::aws::fake"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
Resources:
2+
CustomResource:
3+
Type: AWS::CloudFormation::CustomResource
4+
Properties:
5+
ServiceToken: "arn::aws::fake"
6+
ServiceTimeout: 60
7+
8+
CustomResource2:
9+
Type: Custom::CustomResource
10+
Properties:
11+
ServiceToken: "arn::aws::fake"
12+
ServiceTimeout: 90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
"""
2+
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
SPDX-License-Identifier: MIT-0
4+
"""
5+
6+
import logging
7+
from test.unit.rules import BaseRuleTestCase
8+
9+
from cfnlint.rules.resources.cloudformation.ServiceTimeout import ServiceTimeout
10+
11+
12+
class TestServiceTimeout(BaseRuleTestCase):
13+
"""Test CloudFormation Nested stack parameters"""
14+
15+
def tearDown(self) -> None:
16+
super().tearDown()
17+
logger = logging.getLogger("cfnlint.decode.decode")
18+
logger.disabled = False
19+
20+
def setUp(self):
21+
"""Setup"""
22+
super(ServiceTimeout, self).setUp()
23+
self.collection.register(ServiceTimeout())
24+
logger = logging.getLogger("cfnlint.decode.decode")
25+
logger.disabled = True
26+
self.success_templates = [
27+
"test/fixtures/templates/good/resources/cloudformation/service_timeout.yaml"
28+
]
29+
30+
def test_file_positive(self):
31+
"""Test Positive"""
32+
self.helper_file_positive()
33+
34+
def test_file_negative(self):
35+
"""Test failure"""
36+
err_count = 2
37+
self.helper_file_negative(
38+
"test/fixtures/templates/bad/resources/cloudformation/service_timeout.yaml",
39+
err_count,
40+
)

0 commit comments

Comments
 (0)