Skip to content

Commit

Permalink
Create rule E3056 to validate HealthCheck (#3671)
Browse files Browse the repository at this point in the history
  • Loading branch information
kddejong committed Sep 7, 2024
1 parent 3b47042 commit 5fb446b
Show file tree
Hide file tree
Showing 3 changed files with 208 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"if": {
"properties": {
"HealthCheckGracePeriodSeconds": {
"type": [
"string",
"integer"
]
}
},
"required": [
"HealthCheckGracePeriodSeconds"
],
"type": "object"
},
"then": {
"if": {
"properties": {
"LoadBalancers": {
"type": "array"
}
}
},
"required": [
"LoadBalancers"
],
"then": {
"properties": {
"LoadBalancers": {
"minItems": 1
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
"""
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
SPDX-License-Identifier: MIT-0
"""

from __future__ import annotations

from typing import Any

import cfnlint.data.schemas.extensions.aws_ecs_service
from cfnlint.jsonschema import ValidationResult
from cfnlint.jsonschema.protocols import Validator
from cfnlint.rules.jsonschema.CfnLintJsonSchema import CfnLintJsonSchema, SchemaDetails


class ServiceHealthCheckGracePeriodSeconds(CfnLintJsonSchema):
id = "E3056"
shortdesc = (
"ECS service using HealthCheckGracePeriodSeconds "
"must also have LoadBalancers specified"
)
description = (
"When using a HealthCheckGracePeriodSeconds on an ECS "
"service, the service must also have a LoadBalancers specified "
"with at least one LoadBalancer in the array."
)
source_url = "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ecs-service.html#cfn-ecs-service-healthcheckgraceperiodseconds"
tags = ["properties", "ecs", "service", "container"]

def __init__(self) -> None:
super().__init__(
keywords=["Resources/AWS::ECS::Service/Properties"],
schema_details=SchemaDetails(
module=cfnlint.data.schemas.extensions.aws_ecs_service,
filename="healthcheckgraceperiodseconds.json",
),
all_matches=True,
)

def validate(
self, validator: Validator, keywords: Any, instance: Any, schema: dict[str, Any]
) -> ValidationResult:

cfn_validator = self.extend_validator(
validator=validator.evolve(
function_filter=validator.function_filter.evolve(
validate_dynamic_references=False,
add_cfn_lint_keyword=False,
)
),
schema=self._schema,
context=validator.context.evolve(
functions=["Fn::If", "Ref"],
strict_types=True,
),
)

yield from self._iter_errors(cfn_validator, instance)
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
"""
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
SPDX-License-Identifier: MIT-0
"""

from collections import deque

import pytest

from cfnlint.jsonschema import ValidationError
from cfnlint.rules.resources.ecs.ServiceHealthCheckGracePeriodSeconds import (
ServiceHealthCheckGracePeriodSeconds,
)


@pytest.fixture(scope="module")
def rule():
rule = ServiceHealthCheckGracePeriodSeconds()
yield rule


@pytest.fixture
def template():
return {
"Conditions": {
"IsUsEast1": {"Fn::Equals": [{"Ref": "AWS::Region"}, "us-east-1"]}
},
}


@pytest.mark.parametrize(
"instance,expected",
[
(
{"HealthCheckGracePeriodSeconds": "Foo", "LoadBalancers": ["Bar"]},
[],
),
(
{"LoadBalancers": []},
[],
),
(
[], # wrong type
[],
),
(
{"HealthCheckGracePeriodSeconds": "Foo", "LoadBalancers": []},
[
ValidationError(
"[] is too short (1)",
rule=ServiceHealthCheckGracePeriodSeconds(),
path=deque(["LoadBalancers"]),
validator="minItems",
schema_path=deque(
["then", "then", "properties", "LoadBalancers", "minItems"]
),
)
],
),
(
{
"HealthCheckGracePeriodSeconds": "Foo",
},
[
ValidationError(
"'LoadBalancers' is a required property",
rule=ServiceHealthCheckGracePeriodSeconds(),
path=deque([]),
validator="required",
schema_path=deque(["then", "required"]),
)
],
),
(
{
"HealthCheckGracePeriodSeconds": "Foo",
"LoadBalancers": [
{"Fn::If": ["IsUsEast1", "Bar", {"Ref": "AWS::NoValue"}]}
],
},
[
ValidationError(
"[] is too short (1)",
rule=ServiceHealthCheckGracePeriodSeconds(),
path=deque(["LoadBalancers"]),
validator="minItems",
schema_path=deque(
["then", "then", "properties", "LoadBalancers", "minItems"]
),
)
],
),
(
{
"HealthCheckGracePeriodSeconds": "Foo",
"LoadBalancers": {
"Fn::If": ["IsUsEast1", ["Bar"], {"Ref": "AWS::NoValue"}]
},
},
[
ValidationError(
"'LoadBalancers' is a required property",
rule=ServiceHealthCheckGracePeriodSeconds(),
path=deque([]),
validator="required",
schema_path=deque(["then", "required"]),
)
],
),
],
)
def test_validate(instance, expected, rule, validator):
errs = list(rule.validate(validator, "", instance, {}))

assert errs == expected, f"Expected {expected} got {errs}"

0 comments on commit 5fb446b

Please sign in to comment.