Skip to content

Commit a8c746e

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 a8c746e

File tree

4 files changed

+121
-0
lines changed

4 files changed

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

0 commit comments

Comments
 (0)