diff --git a/docs/rules.md b/docs/rules.md index 9b6a65684b..71888b87c9 100644 --- a/docs/rules.md +++ b/docs/rules.md @@ -132,6 +132,7 @@ The following **151** rules are applied by this linter: | [E3041](../src/cfnlint/rules/resources/route53/RecordSetName.py) | RecordSet HostedZoneName is a superdomain of Name | In a RecordSet, the HostedZoneName must be a superdomain of the Name being validated | | [Source](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-route53-recordset.html#cfn-route53-recordset-name) | `resource`,`properties`,`route53` | | [E3042](../src/cfnlint/rules/resources/ecs/TaskDefinitionEssentialContainer.py) | Check at least one essential container is specified | Check that every TaskDefinition specifies at least one essential container | | [Source](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-taskdefinition-containerdefinitions.html#cfn-ecs-taskdefinition-containerdefinition-essential) | `properties`,`ecs`,`task`,`container`,`fargate` | | [E3043](../src/cfnlint/rules/resources/cloudformation/NestedStackParameters.py) | Validate parameters for in a nested stack | Evalute if parameters for a nested stack are specified and if parameters are specified for a nested stack that aren't required. | | [Source](https://github.com/awslabs/cfn-python-lint) | `resources`,`cloudformation` | +| [E3044](../src/cfnlint/rules/resources/ecs/FargateDeploymentSchedulingStrategy.py) | Validate scheduling strategy for Fargate services | Fargate services currently only support REPLICA scheduling strategy | | [Source](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ecs-service.html#cfn-ecs-service-schedulingstrategy) | `resources`,`container`, `fargate`, `service` | | [E3050](../src/cfnlint/rules/resources/iam/RefWithPath.py) | Check if REFing to a IAM resource with path set | Some resources don't support looking up the IAM resource by name. This check validates when a REF is being used and the Path is not '/' | | [Source](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements.html) | `properties`,`iam` | | [E3502](../src/cfnlint/rules/resources/properties/JsonSize.py) | Check if a JSON Object is within size limits | Validate properties that are JSON values so that their length is within the limits | | [Source](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cloudformation-limits.html) | `resources`,`limits`,`json` | | [E3503](../src/cfnlint/rules/resources/certificatemanager/DomainValidationOptions.py) | ValidationDomain is superdomain of DomainName | In ValidationDomainOptions, the ValidationDomain must be a superdomain of the DomainName being validated | | [Source](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-certificatemanager-certificate-domainvalidationoption.html#cfn-certificatemanager-certificate-domainvalidationoption-validationdomain) | `certificate`,`certificatemanager`,`domainvalidationoptions`,`validationdomain` | diff --git a/src/cfnlint/rules/resources/ecs/FargateDeploymentSchedulingStrategy.py b/src/cfnlint/rules/resources/ecs/FargateDeploymentSchedulingStrategy.py new file mode 100644 index 0000000000..1e00011294 --- /dev/null +++ b/src/cfnlint/rules/resources/ecs/FargateDeploymentSchedulingStrategy.py @@ -0,0 +1,32 @@ +""" +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: MIT-0 +""" +from cfnlint.rules import CloudFormationLintRule, RuleMatch + + +class FargateDeploymentSchedulingStrategy(CloudFormationLintRule): + id = "E3044" + shortdesc = "Check Fargate service scheduling strategy" + description = "Check that Fargate service scheduling strategy is REPLICA" + source_url = "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ecs-service.html#cfn-ecs-service-schedulingstrategy" + tags = ["properties", "ecs", "service", "container", "fargate"] + + def match(self, cfn): + matches = [] + ecs_services = cfn.get_resource_properties(["AWS::ECS::Service"]) + + for ecs_service in ecs_services: + path = ecs_service["Path"] + properties = ecs_service["Value"] + if isinstance(properties, dict): + launch_type = properties.get("LaunchType", None) + if isinstance(launch_type, str) and launch_type == "Fargate": + scheduling_strategy = properties.get("SchedulingStrategy", None) + if ( + isinstance(scheduling_strategy, str) + and scheduling_strategy != "REPLICA" + ): + error_message = f"Fargate service only support REPLICA as scheduling strategy at {'/'.join(map(str, path))}" + matches.append(RuleMatch(path, error_message)) + return matches diff --git a/src/cfnlint/rules/resources/ecs/TaskDefinitionEssentialContainer.py b/src/cfnlint/rules/resources/ecs/TaskDefinitionEssentialContainer.py index 598037178e..2ffb0b6285 100644 --- a/src/cfnlint/rules/resources/ecs/TaskDefinitionEssentialContainer.py +++ b/src/cfnlint/rules/resources/ecs/TaskDefinitionEssentialContainer.py @@ -41,8 +41,10 @@ def match(self, cfn): has_essential_container = True if not has_essential_container: - message = "No essential containers defined for {0}" - rule_match = RuleMatch(path, message.format("/".join(map(str, path)))) + error_message = ( + f"No essential containers defined for {'/'.join(map(str, path))}" + ) + rule_match = RuleMatch(path, error_message) matches.append(rule_match) return matches diff --git a/test/fixtures/templates/bad/resources/ecs/test_fargate_scheduling_strategy.yaml b/test/fixtures/templates/bad/resources/ecs/test_fargate_scheduling_strategy.yaml new file mode 100644 index 0000000000..f5246cccd7 --- /dev/null +++ b/test/fixtures/templates/bad/resources/ecs/test_fargate_scheduling_strategy.yaml @@ -0,0 +1,6 @@ +Resources: + Service1: + Type: AWS::ECS::Service + Properties: + LaunchType: Fargate + SchedulingStrategy: DAEMON diff --git a/test/fixtures/templates/good/resources/ecs/test_fargate_scheduling_strategy.yaml b/test/fixtures/templates/good/resources/ecs/test_fargate_scheduling_strategy.yaml new file mode 100644 index 0000000000..0331566ce8 --- /dev/null +++ b/test/fixtures/templates/good/resources/ecs/test_fargate_scheduling_strategy.yaml @@ -0,0 +1,21 @@ +Resources: + Service1: + Type: AWS::ECS::Service + Properties: + LaunchType: Fargate + SchedulingStrategy: REPLICA + Service2: + Type: AWS::ECS::Service + Properties: + LaunchType: EC2 + SchedulingStrategy: DAEMON + Service3: + Type: AWS::ECS::Service + Properties: + LaunchType: EXTERNAL + SchedulingStrategy: DAEMON + Service4: + Type: AWS::ECS::Service + Properties: + LaunchType: !Join ["", ["FAR", "GATE"]] + SchedulingStrategy: DAEMON diff --git a/test/unit/rules/resources/ecs/test_fargate_service_scheduling_strategy.py b/test/unit/rules/resources/ecs/test_fargate_service_scheduling_strategy.py new file mode 100644 index 0000000000..06adc30a4d --- /dev/null +++ b/test/unit/rules/resources/ecs/test_fargate_service_scheduling_strategy.py @@ -0,0 +1,31 @@ +""" +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: MIT-0 +""" +from test.unit.rules import BaseRuleTestCase + +from cfnlint.rules.resources.ecs.FargateDeploymentSchedulingStrategy import ( + FargateDeploymentSchedulingStrategy, +) + + +class TestFargateServiceSchedulingStrategy(BaseRuleTestCase): + """ + Test that Fargate service has correct scheduling strategy (REPLICA) + """ + + def setUp(self): + super(TestFargateServiceSchedulingStrategy, self).setUp() + self.collection.register(FargateDeploymentSchedulingStrategy()) + self.success_templates = [ + "test/fixtures/templates/good/resources/ecs/test_fargate_scheduling_strategy.yaml" + ] + + def test_file_positive(self): + self.helper_file_positive() + + def test_file_negative(self): + self.helper_file_negative( + "test/fixtures/templates/bad/resources/ecs/test_fargate_scheduling_strategy.yaml", + 1, + )