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,
+ )