Repository: ansible-collections/community.aws

Issue:
Capacity Provider Strategy for ECS Services
### Summary

Hi All,
the ecs_service module does not support the capacity provider strategy. The only possibility is to use the default strategy provider for the ECS cluster removing the launch_type parameter in the task definition for the module.

Please add this feature, is extremely important for the cluster autoscaler with EC2 instance.

Thank you.

-------

https://docs.aws.amazon.com/cli/latest/reference/ecs/create-service.html

Option:
--capacity-provider-strategy (list)

The capacity provider strategy to use for the service.

If a capacityProviderStrategy is specified, the launchType parameter must be omitted. If no capacityProviderStrategy or launchType is specified, the defaultCapacityProviderStrategy for the cluster is used.

A capacity provider strategy may contain a maximum of 6 capacity providers.

### Issue Type

Feature Idea

### Component Name

community.aws.ecs_service

### Additional Information

<!--- Paste example playbooks or commands between quotes below -->
```yaml (paste below)

```


### Code of Conduct

- [X] I agree to follow the Ansible Code of Conduct


Discussion:
Files identified in the description:
None

If these files are inaccurate, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->

Resolving patch:
diff --git a/plugins/modules/ecs_service.py b/plugins/modules/ecs_service.py
--- a/plugins/modules/ecs_service.py
+++ b/plugins/modules/ecs_service.py
@@ -159,6 +159,26 @@
         required: false
         choices: ["EC2", "FARGATE"]
         type: str
+    capacity_provider_strategy:
+        version_added: 4.0.0
+        description:
+          - The capacity provider strategy to use with your service. You can specify a maximum of 6 providers per strategy.
+        required: false
+        type: list
+        elements: dict
+        suboptions:
+            capacity_provider:
+                description:
+                  - Name of capacity provider.
+                type: str
+            weight:
+                description:
+                  - The relative percentage of the total number of launched tasks that should use the specified provider.
+                type: int
+            base:
+                description:
+                  - How many tasks, at a minimum, should use the specified provider.
+                type: int
     platform_version:
         type: str
         description:
@@ -250,6 +270,18 @@
     placement_strategy:
       - type: binpack
         field: memory
+
+# With capacity_provider_strategy (added in version 4.0)
+- community.aws.ecs_service:
+    state: present
+    name: test-service
+    cluster: test-cluster
+    task_definition: test-task-definition
+    desired_count: 1
+    capacity_provider_strategy:
+      - capacity_provider: test-capacity-provider-1
+        weight: 1
+        base: 0
 '''
 
 RETURN = r'''
@@ -258,6 +290,24 @@
     returned: when creating a service
     type: complex
     contains:
+        capacityProviderStrategy:
+            version_added: 4.0.0
+            description: The capacity provider strategy to use with your service.
+            returned: always
+            type: complex
+            contains:
+                base:
+                    description: How many tasks, at a minimum, should use the specified provider.
+                    returned: always
+                    type: int
+                capacityProvider:
+                    description: Name of capacity provider.
+                    returned: always
+                    type: str
+                weight:
+                    description: The relative percentage of the total number of launched tasks that should use the specified provider.
+                    returned: always
+                    type: int
         clusterArn:
             description: The Amazon Resource Name (ARN) of the of the cluster that hosts the service.
             returned: always
@@ -575,7 +625,7 @@ def create_service(self, service_name, cluster_name, task_definition, load_balan
                        desired_count, client_token, role, deployment_configuration,
                        placement_constraints, placement_strategy, health_check_grace_period_seconds,
                        network_configuration, service_registries, launch_type, platform_version,
-                       scheduling_strategy):
+                       scheduling_strategy, capacity_provider_strategy):
 
         params = dict(
             cluster=cluster_name,
@@ -607,7 +657,8 @@ def create_service(self, service_name, cluster_name, task_definition, load_balan
         # desired count is not required if scheduling strategy is daemon
         if desired_count is not None:
             params['desiredCount'] = desired_count
-
+        if capacity_provider_strategy:
+            params['capacityProviderStrategy'] = capacity_provider_strategy
         if scheduling_strategy:
             params['schedulingStrategy'] = scheduling_strategy
         response = self.ecs.create_service(**params)
@@ -615,7 +666,7 @@ def create_service(self, service_name, cluster_name, task_definition, load_balan
 
     def update_service(self, service_name, cluster_name, task_definition,
                        desired_count, deployment_configuration, network_configuration,
-                       health_check_grace_period_seconds, force_new_deployment):
+                       health_check_grace_period_seconds, force_new_deployment, capacity_provider_strategy):
         params = dict(
             cluster=cluster_name,
             service=service_name,
@@ -625,6 +676,8 @@ def update_service(self, service_name, cluster_name, task_definition,
             params['networkConfiguration'] = network_configuration
         if force_new_deployment:
             params['forceNewDeployment'] = force_new_deployment
+        if capacity_provider_strategy:
+            params['capacityProviderStrategy'] = capacity_provider_strategy
         if health_check_grace_period_seconds is not None:
             params['healthCheckGracePeriodSeconds'] = health_check_grace_period_seconds
         # desired count is not required if scheduling strategy is daemon
@@ -703,19 +756,34 @@ def main():
         launch_type=dict(required=False, choices=['EC2', 'FARGATE']),
         platform_version=dict(required=False, type='str'),
         service_registries=dict(required=False, type='list', default=[], elements='dict'),
-        scheduling_strategy=dict(required=False, choices=['DAEMON', 'REPLICA'])
+        scheduling_strategy=dict(required=False, choices=['DAEMON', 'REPLICA']),
+        capacity_provider_strategy=dict(
+            required=False,
+            type='list',
+            default=[],
+            elements='dict',
+            options=dict(
+                capacity_provider=dict(type='str'),
+                weight=dict(type='int'),
+                base=dict(type='int')
+            )
+        )
     )
 
     module = AnsibleAWSModule(argument_spec=argument_spec,
                               supports_check_mode=True,
                               required_if=[('state', 'present', ['task_definition']),
                                            ('launch_type', 'FARGATE', ['network_configuration'])],
-                              required_together=[['load_balancers', 'role']])
+                              required_together=[['load_balancers', 'role']],
+                              mutually_exclusive=[['launch_type', 'capacity_provider_strategy']])
 
     if module.params['state'] == 'present' and module.params['scheduling_strategy'] == 'REPLICA':
         if module.params['desired_count'] is None:
             module.fail_json(msg='state is present, scheduling_strategy is REPLICA; missing desired_count')
 
+    if len(module.params['capacity_provider_strategy']) > 6:
+        module.fail_json(msg='AWS allows a maximum of six capacity providers in the strategy.')
+
     service_mgr = EcsServiceManager(module)
     if module.params['network_configuration']:
         network_configuration = service_mgr.format_network_configuration(module.params['network_configuration'])
@@ -727,6 +795,7 @@ def main():
 
     deploymentConfiguration = snake_dict_to_camel_dict(deployment_configuration)
     serviceRegistries = list(map(snake_dict_to_camel_dict, module.params['service_registries']))
+    capacityProviders = list(map(snake_dict_to_camel_dict, module.params['capacity_provider_strategy']))
 
     try:
         existing = service_mgr.describe_service(module.params['cluster'], module.params['name'])
@@ -775,7 +844,12 @@ def main():
                     if module.params['service_registries']:
                         if (existing['serviceRegistries'] or []) != serviceRegistries:
                             module.fail_json(msg="It is not possible to update the service registries of an existing service")
-
+                    if module.params['capacity_provider_strategy']:
+                        if 'launchType' in existing.keys():
+                            module.fail_json(msg="It is not possible to change an existing service from launch_type to capacity_provider_strategy.")
+                    if module.params['launch_type']:
+                        if 'capacityProviderStrategy' in existing.keys():
+                            module.fail_json(msg="It is not possible to change an existing service from capacity_provider_strategy to launch_type.")
                     if (existing['loadBalancers'] or []) != loadBalancers:
                         module.fail_json(msg="It is not possible to update the load balancers of an existing service")
 
@@ -787,7 +861,9 @@ def main():
                                                           deploymentConfiguration,
                                                           network_configuration,
                                                           module.params['health_check_grace_period_seconds'],
-                                                          module.params['force_new_deployment'])
+                                                          module.params['force_new_deployment'],
+                                                          capacityProviders
+                                                          )
 
                 else:
                     try:
@@ -806,7 +882,8 @@ def main():
                                                               serviceRegistries,
                                                               module.params['launch_type'],
                                                               module.params['platform_version'],
-                                                              module.params['scheduling_strategy']
+                                                              module.params['scheduling_strategy'],
+                                                              capacityProviders
                                                               )
                     except botocore.exceptions.ClientError as e:
                         module.fail_json_aws(e, msg="Couldn't create service")


Test patch: