From daf73f26fce3ae2bc2610cb23da9751600461fe6 Mon Sep 17 00:00:00 2001 From: Kevin DeJong Date: Thu, 4 Jul 2024 09:02:31 -0700 Subject: [PATCH] Add rule E3047 to validate ECS Fargate cpu/memory --- .../fargate_cpu_memory.json | 114 ++++++++++++++++++ .../rules/resources/ecs/FargateCpuMemory.py | 41 +++++++ .../ecs/test_ecs_fargate_cpu_memory.py | 104 ++++++++++++++++ 3 files changed, 259 insertions(+) create mode 100644 src/cfnlint/data/schemas/extensions/aws_ecs_taskdefinition/fargate_cpu_memory.json create mode 100644 src/cfnlint/rules/resources/ecs/FargateCpuMemory.py create mode 100644 test/unit/rules/resources/ecs/test_ecs_fargate_cpu_memory.py diff --git a/src/cfnlint/data/schemas/extensions/aws_ecs_taskdefinition/fargate_cpu_memory.json b/src/cfnlint/data/schemas/extensions/aws_ecs_taskdefinition/fargate_cpu_memory.json new file mode 100644 index 0000000000..b5e0aa8692 --- /dev/null +++ b/src/cfnlint/data/schemas/extensions/aws_ecs_taskdefinition/fargate_cpu_memory.json @@ -0,0 +1,114 @@ +{ + "if": { + "properties": { + "RequiresCompatibilities": { + "type": "array", + "contains": { + "type": "string", + "enum": ["FARGATE"] + } + }, + "Cpu": { + "type": ["string", "integer"] + }, + "Memory": { + "type": ["string", "integer"] + } + }, + "required": ["RequiresCompatibilities", "Cpu", "Memory"] + }, + "then": { + "anyOf": [ + { + "properties": { + "Cpu": { + "enum": ["256", 256] + }, + "Memory": { + "enum": [ + "512", + "1024", + "2048", + 512, + 1024, + 2048 + ] + } + } + }, + { + "properties": { + "Cpu": { + "enum": ["512", 512] + }, + "Memory": { + "minimum": 1024, + "maximum": 4096, + "multipleOf": 1024 + } + } + }, + { + "properties": { + "Cpu": { + "enum": ["1024", 1024] + }, + "Memory": { + "minimum": 2048, + "maximum": 8192, + "multipleOf": 1024 + } + } + }, + { + "properties": { + "Cpu": { + "enum": ["2048", 2048] + }, + "Memory": { + "minimum": 4096, + "maximum": 16384, + "multipleOf": 1024 + } + } + }, + { + "properties": { + "Cpu": { + "enum": ["4096", 4096] + }, + "Memory": { + "minimum": 8192, + "maximum": 30720, + "multipleOf": 1024 + + } + } + }, + { + "properties": { + "Cpu": { + "enum": ["8192", 8192] + }, + "Memory": { + "minimum": 16384, + "maximum": 61440, + "multipleOf": 4096 + } + } + }, + { + "properties": { + "Cpu": { + "enum": ["16384", 16384] + }, + "Memory": { + "minimum": 32768, + "maximum": 122880, + "multipleOf": 8192 + } + } + } + ] + } +} diff --git a/src/cfnlint/rules/resources/ecs/FargateCpuMemory.py b/src/cfnlint/rules/resources/ecs/FargateCpuMemory.py new file mode 100644 index 0000000000..db684e554c --- /dev/null +++ b/src/cfnlint/rules/resources/ecs/FargateCpuMemory.py @@ -0,0 +1,41 @@ +""" +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_taskdefinition +from cfnlint.jsonschema import ValidationError +from cfnlint.rules.jsonschema.CfnLintJsonSchema import CfnLintJsonSchema, SchemaDetails + + +class FargateCpuMemory(CfnLintJsonSchema): + id = "E3047" + shortdesc = ( + "Validate ECS Fargate tasks have the right " "combination of CPU and memory" + ) + description = ( + "When using a ECS Fargate task there is a specfic combination " + "of memory and cpu that can be used" + ) + source_url = "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ecs-taskdefinition.html#cfn-ecs-taskdefinition-memory" + tags = ["properties", "ecs", "service", "container", "fargate"] + + def __init__(self) -> None: + super().__init__( + keywords=["Resources/AWS::ECS::TaskDefinition/Properties"], + schema_details=SchemaDetails( + module=cfnlint.data.schemas.extensions.aws_ecs_taskdefinition, + filename="fargate_cpu_memory.json", + ), + ) + + def message(self, instance: Any, err: ValidationError) -> str: + return ( + f"Cpu {instance.get('Cpu')!r} is not " + "compatible with memory " + f"{instance.get('Memory')!r}" + ) diff --git a/test/unit/rules/resources/ecs/test_ecs_fargate_cpu_memory.py b/test/unit/rules/resources/ecs/test_ecs_fargate_cpu_memory.py new file mode 100644 index 0000000000..00ae803655 --- /dev/null +++ b/test/unit/rules/resources/ecs/test_ecs_fargate_cpu_memory.py @@ -0,0 +1,104 @@ +""" +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.FargateCpuMemory import FargateCpuMemory + + +@pytest.fixture(scope="module") +def rule(): + rule = FargateCpuMemory() + yield rule + + +@pytest.mark.parametrize( + "instance,expected", + [ + ( + { + "RequiresCompatibilities": ["FARGATE"], + "Cpu": 256, + "Memory": "512", + }, + [], + ), + ( + { + "RequiresCompatibilities": ["FARGATE"], + "Cpu": "512", + "Memory": 1024, + }, + [], + ), + ( + { + "RequiresCompatibilities": ["FARGATE"], + "Cpu": "1024", + "Memory": "2048", + }, + [], + ), + ( + { + "RequiresCompatibilities": ["FARGATE"], + "Cpu": 2048, + "Memory": 4096, + }, + [], + ), + ( + { + "RequiresCompatibilities": ["FARGATE"], + "Cpu": 4096, + "Memory": 30720, + }, + [], + ), + ( + { + "RequiresCompatibilities": ["FARGATE"], + "Cpu": 8192, + "Memory": 16384, + }, + [], + ), + ( + { + "RequiresCompatibilities": ["FARGATE"], + "Cpu": 16384, + "Memory": 122880, + }, + [], + ), + ( + { + "RequiresCompatibilities": ["FARGATE"], + "Cpu": 16384, + "Memory": 123904, + }, + [ + ValidationError( + "Cpu 16384 is not compatible with memory 123904", + rule=FargateCpuMemory(), + path=deque([]), + validator="anyOf", + schema_path=deque(["then", "anyOf"]), + ) + ], + ), + ], +) +def test_validate(instance, expected, rule, validator): + errs = list(rule.validate(validator, "", instance, {})) + for err in errs: + print(err.schema_path) + print(err.validator) + print(err.rule) + print(err.path) + assert errs == expected, f"Expected {expected} got {errs}"