Skip to content

Commit 5fb446b

Browse files
authored
Create rule E3056 to validate HealthCheck (#3671)
1 parent 3b47042 commit 5fb446b

File tree

3 files changed

+208
-0
lines changed

3 files changed

+208
-0
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"if": {
3+
"properties": {
4+
"HealthCheckGracePeriodSeconds": {
5+
"type": [
6+
"string",
7+
"integer"
8+
]
9+
}
10+
},
11+
"required": [
12+
"HealthCheckGracePeriodSeconds"
13+
],
14+
"type": "object"
15+
},
16+
"then": {
17+
"if": {
18+
"properties": {
19+
"LoadBalancers": {
20+
"type": "array"
21+
}
22+
}
23+
},
24+
"required": [
25+
"LoadBalancers"
26+
],
27+
"then": {
28+
"properties": {
29+
"LoadBalancers": {
30+
"minItems": 1
31+
}
32+
}
33+
}
34+
}
35+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
"""
2+
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
SPDX-License-Identifier: MIT-0
4+
"""
5+
6+
from __future__ import annotations
7+
8+
from typing import Any
9+
10+
import cfnlint.data.schemas.extensions.aws_ecs_service
11+
from cfnlint.jsonschema import ValidationResult
12+
from cfnlint.jsonschema.protocols import Validator
13+
from cfnlint.rules.jsonschema.CfnLintJsonSchema import CfnLintJsonSchema, SchemaDetails
14+
15+
16+
class ServiceHealthCheckGracePeriodSeconds(CfnLintJsonSchema):
17+
id = "E3056"
18+
shortdesc = (
19+
"ECS service using HealthCheckGracePeriodSeconds "
20+
"must also have LoadBalancers specified"
21+
)
22+
description = (
23+
"When using a HealthCheckGracePeriodSeconds on an ECS "
24+
"service, the service must also have a LoadBalancers specified "
25+
"with at least one LoadBalancer in the array."
26+
)
27+
source_url = "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ecs-service.html#cfn-ecs-service-healthcheckgraceperiodseconds"
28+
tags = ["properties", "ecs", "service", "container"]
29+
30+
def __init__(self) -> None:
31+
super().__init__(
32+
keywords=["Resources/AWS::ECS::Service/Properties"],
33+
schema_details=SchemaDetails(
34+
module=cfnlint.data.schemas.extensions.aws_ecs_service,
35+
filename="healthcheckgraceperiodseconds.json",
36+
),
37+
all_matches=True,
38+
)
39+
40+
def validate(
41+
self, validator: Validator, keywords: Any, instance: Any, schema: dict[str, Any]
42+
) -> ValidationResult:
43+
44+
cfn_validator = self.extend_validator(
45+
validator=validator.evolve(
46+
function_filter=validator.function_filter.evolve(
47+
validate_dynamic_references=False,
48+
add_cfn_lint_keyword=False,
49+
)
50+
),
51+
schema=self._schema,
52+
context=validator.context.evolve(
53+
functions=["Fn::If", "Ref"],
54+
strict_types=True,
55+
),
56+
)
57+
58+
yield from self._iter_errors(cfn_validator, instance)
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
"""
2+
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
SPDX-License-Identifier: MIT-0
4+
"""
5+
6+
from collections import deque
7+
8+
import pytest
9+
10+
from cfnlint.jsonschema import ValidationError
11+
from cfnlint.rules.resources.ecs.ServiceHealthCheckGracePeriodSeconds import (
12+
ServiceHealthCheckGracePeriodSeconds,
13+
)
14+
15+
16+
@pytest.fixture(scope="module")
17+
def rule():
18+
rule = ServiceHealthCheckGracePeriodSeconds()
19+
yield rule
20+
21+
22+
@pytest.fixture
23+
def template():
24+
return {
25+
"Conditions": {
26+
"IsUsEast1": {"Fn::Equals": [{"Ref": "AWS::Region"}, "us-east-1"]}
27+
},
28+
}
29+
30+
31+
@pytest.mark.parametrize(
32+
"instance,expected",
33+
[
34+
(
35+
{"HealthCheckGracePeriodSeconds": "Foo", "LoadBalancers": ["Bar"]},
36+
[],
37+
),
38+
(
39+
{"LoadBalancers": []},
40+
[],
41+
),
42+
(
43+
[], # wrong type
44+
[],
45+
),
46+
(
47+
{"HealthCheckGracePeriodSeconds": "Foo", "LoadBalancers": []},
48+
[
49+
ValidationError(
50+
"[] is too short (1)",
51+
rule=ServiceHealthCheckGracePeriodSeconds(),
52+
path=deque(["LoadBalancers"]),
53+
validator="minItems",
54+
schema_path=deque(
55+
["then", "then", "properties", "LoadBalancers", "minItems"]
56+
),
57+
)
58+
],
59+
),
60+
(
61+
{
62+
"HealthCheckGracePeriodSeconds": "Foo",
63+
},
64+
[
65+
ValidationError(
66+
"'LoadBalancers' is a required property",
67+
rule=ServiceHealthCheckGracePeriodSeconds(),
68+
path=deque([]),
69+
validator="required",
70+
schema_path=deque(["then", "required"]),
71+
)
72+
],
73+
),
74+
(
75+
{
76+
"HealthCheckGracePeriodSeconds": "Foo",
77+
"LoadBalancers": [
78+
{"Fn::If": ["IsUsEast1", "Bar", {"Ref": "AWS::NoValue"}]}
79+
],
80+
},
81+
[
82+
ValidationError(
83+
"[] is too short (1)",
84+
rule=ServiceHealthCheckGracePeriodSeconds(),
85+
path=deque(["LoadBalancers"]),
86+
validator="minItems",
87+
schema_path=deque(
88+
["then", "then", "properties", "LoadBalancers", "minItems"]
89+
),
90+
)
91+
],
92+
),
93+
(
94+
{
95+
"HealthCheckGracePeriodSeconds": "Foo",
96+
"LoadBalancers": {
97+
"Fn::If": ["IsUsEast1", ["Bar"], {"Ref": "AWS::NoValue"}]
98+
},
99+
},
100+
[
101+
ValidationError(
102+
"'LoadBalancers' is a required property",
103+
rule=ServiceHealthCheckGracePeriodSeconds(),
104+
path=deque([]),
105+
validator="required",
106+
schema_path=deque(["then", "required"]),
107+
)
108+
],
109+
),
110+
],
111+
)
112+
def test_validate(instance, expected, rule, validator):
113+
errs = list(rule.validate(validator, "", instance, {}))
114+
115+
assert errs == expected, f"Expected {expected} got {errs}"

0 commit comments

Comments
 (0)