Repository: ansible-collections/community.aws

Issue:
ecs_service - Support force_new_deployment without having to specify a task definition
### Summary

Sometimes, it can be helpful to just restart all tasks from a service, without having to deal with all tasks definitions (since they do not need to change).

For that purpose, the aws cli offers the following possibility : 
```
aws ecs update-service --cluster cluster_name --service service_name --force-new-deployment
```
This is particularly interesting when we want some external files to be taken (but the task definition itself does not need any change).

We can note that boto3 already support this option (at least since `v1.14.2` i'm working with) : 
```python
import boto3
client = boto3.client('ecs')
client.update_service(cluster='cluster_name', service='service_name', forceNewDeployment=True)
```

However, the `ecs_service` module can not do that since when we specify `state: present`, we get the following error 
```
fatal: [localhost]: FAILED! => {"changed": false, "msg": "state is present but all of the following are missing: task_definition"}
```

### Issue Type

Feature Idea

### Component Name

ecs_service

### Additional Information

An rough idea would be to add an additional parameter (like `preserve_tasks_definitions`) which would, when true, avoid the check about the presence of `task_definition`.


### Code of Conduct

- [X] I agree to follow the Ansible Code of Conduct


Discussion:
Files identified in the description:
* [`plugins/modules/ecs_service.py`](https://github.com/['ansible-collections/amazon.aws', 'ansible-collections/community.aws', 'ansible-collections/community.vmware']/blob/main/plugins/modules/ecs_service.py)

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 --->
cc @Java1Guy @jillr @kaczynskid @markuman @s-hertel @tremble @zacblazic
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
I forgot to mention : 
It is indeed workaroundable but requires some more tasks  to retrieve informations about loadBalancers & taskdefinition.

```ansible
- name: Retrieve service details
  community.aws.ecs_service_info:
    cluster: "{{ cluster_name }}"
    service: "{{ service_name }}"
    details: true
  register: service_details

- name: Reload ECS service
  community.aws.ecs_service:
    cluster: "{{ cluster_name }}"
    name: "{{ service_name }}"
    state: present
    force_new_deployment: yes
    load_balancers: "{{ service_details.services | map(attribute='loadBalancers') | first }}"
    task_definition: "{{ service_details.services | map(attribute='taskDefinition') | first }}"
```

The main idea of this feature request it to improve the user experience by not having to deal with data we do not care about at the moment.

> We can note that boto3 already support this option (at least since `v1.14.2` i'm working with) :
> 
> ```python
> import boto3
> client = boto3.client('ecs')
> client.update_service(cluster='cluster_name', service='service_name', forceNewDeployment=True)
> ```
> 
> However, the `ecs_service` module can not do that since when we specify `state: present`, we get the following error
> 
> ```
> fatal: [localhost]: FAILED! => {"changed": false, "msg": "state is present but all of the following are missing: task_definition"}
> ```

So a new boolean parameter `force_new_deployment` must be introduced.  
When it got no default value, it can be mutually exclusive with the `state`  parameter.  

@giom-l do you have some time to implement this new feature?
Hi,

Not this week but I can take a look at it the next one, sure.
Just to be sure about what to do : you write about a new Boolean parameter,
but there is already an existing one with this name :
https://docs.ansible.com/ansible/latest/collections/community/aws/ecs_service_module.html#parameter-force_new_deployment

It is just the behaviour of this one that should be changed, right ?
(NB : I haven't the whole code in front of me right now so I may have
missed something)
Ah sorry. I thought the entire parameter was missing.  
So yes, imo the internally logic must just be fixed, so that  `force_new_deployment: yes` works also on existing service without the need to specify the taskdefinition.

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
@@ -44,7 +44,7 @@
     task_definition:
         description:
           - The task definition the service will run.
-          - This parameter is required when I(state=present).
+          - This parameter is required when I(state=present) unless I(force_new_deployment=True).
           - This parameter is ignored when updating a service with a C(CODE_DEPLOY) deployment controller in which case
             the task definition is managed by Code Pipeline and cannot be updated.
         required: false
@@ -971,14 +971,15 @@ def main():
 
     module = AnsibleAWSModule(argument_spec=argument_spec,
                               supports_check_mode=True,
-                              required_if=[('state', 'present', ['task_definition']),
-                                           ('launch_type', 'FARGATE', ['network_configuration'])],
+                              required_if=[('launch_type', 'FARGATE', ['network_configuration'])],
                               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:
+    if module.params['state'] == 'present':
+        if module.params['scheduling_strategy'] == 'REPLICA' and module.params['desired_count'] is None:
             module.fail_json(msg='state is present, scheduling_strategy is REPLICA; missing desired_count')
+        if module.params['task_definition'] is None and not module.params['force_new_deployment']:
+            module.fail_json(msg='Either task_definition or force_new_deployment is required when status is present.')
 
     if len(module.params['capacity_provider_strategy']) > 6:
         module.fail_json(msg='AWS allows a maximum of six capacity providers in the strategy.')
@@ -1075,6 +1076,9 @@ def main():
 
                     updatedLoadBalancers = loadBalancers if existing['deploymentController']['type'] == 'ECS' else []
 
+                    if task_definition is None and module.params['force_new_deployment']:
+                        task_definition = existing['taskDefinition']
+
                     # update required
                     response = service_mgr.update_service(module.params['name'],
                                                           module.params['cluster'],


Test patch:
diff --git a/tests/integration/targets/ecs_cluster/tasks/20_ecs_service.yml b/tests/integration/targets/ecs_cluster/tasks/20_ecs_service.yml
--- a/tests/integration/targets/ecs_cluster/tasks/20_ecs_service.yml
+++ b/tests/integration/targets/ecs_cluster/tasks/20_ecs_service.yml
@@ -112,6 +112,32 @@
     that:
       - ecs_service_again.changed
 
+- name: force_new_deployment should work without providing a task_definition
+  vars:
+    ansible_python_interpreter: "{{ botocore_virtualenv_interpreter }}"
+  ecs_service:
+    state: present
+    force_new_deployment: yes
+    name: "{{ ecs_service_name }}"
+    cluster: "{{ ecs_cluster_name }}"
+    desired_count: 1
+    deployment_configuration: "{{ ecs_service_deployment_configuration }}"
+    placement_strategy: "{{ ecs_service_placement_strategy }}"
+    placement_constraints:
+      - type: distinctInstance
+    health_check_grace_period_seconds: "{{ ecs_service_health_check_grace_period }}"
+    load_balancers:
+      - targetGroupArn: "{{ elb_target_group_instance.target_group_arn }}"
+        containerName: "{{ ecs_task_name }}"
+        containerPort: "{{ ecs_task_container_port }}"
+    role: "{{ ecs_service_role_name }}"
+  register: ecs_service_notaskdef
+
+- name: check that ECS service changed again due to force_new_deployment with no task definition
+  assert:
+    that:
+      - ecs_service_notaskdef.changed
+
 - name: attempt to use ECS network configuration on task definition without awsvpc network_mode (expected to fail)
   vars:
     ansible_python_interpreter: "{{ botocore_virtualenv_interpreter }}"