Repository: getmoto/moto
Pull request #7598 (2024-04-15 19:46:09)

ECS: update service use all parameters
Resolves https://github.com/getmoto/moto/issues/7564

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/getmoto/moto/pull/7598?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 94.18%. Comparing base [(`9aef694`)](https://app.codecov.io/gh/getmoto/moto/commit/9aef6944da6a6f1b84be99cefb34f1724e6ca58f?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) to head [(`b3b04bc`)](https://app.codecov.io/gh/getmoto/moto/pull/7598?dropdown=coverage&src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto).
> Report is 183 commits behind head on master.


<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #7598      +/-   ##
==========================================
- Coverage   95.88%   94.18%   -1.71%     
==========================================
  Files         843     1072     +229     
  Lines       82578    92095    +9517     
==========================================
+ Hits        79178    86737    +7559     
- Misses       3400     5358    +1958     
```

| [Flag](https://app.codecov.io/gh/getmoto/moto/pull/7598/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) | Coverage Δ | |
|---|---|---|
| [servertests](https://app.codecov.io/gh/getmoto/moto/pull/7598/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) | `29.49% <5.88%> (-3.03%)` | :arrow_down: |
| [unittests](https://app.codecov.io/gh/getmoto/moto/pull/7598/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) | `94.15% <100.00%> (-1.70%)` | :arrow_down: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto#carryforward-flags-in-the-pull-request-comment) to find out more.


</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/getmoto/moto/pull/7598?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto).

rafcio19: The failing tests seem unrelated to this PR 🤷‍♂️

Patch:
diff --git a/moto/ecs/models.py b/moto/ecs/models.py
index 72acea49f..adfc7b5ec 100644
--- a/moto/ecs/models.py
+++ b/moto/ecs/models.py
@@ -703,7 +703,12 @@ class Service(BaseObject, CloudFormationModel):
             )
         else:
             return ecs_backend.update_service(
-                cluster_name, service_name, task_definition, desired_count
+                {
+                    "cluster": cluster_name,
+                    "service": service_name,
+                    "task_definition": task_definition,
+                    "desired_count": desired_count,
+                }
             )
 
     @classmethod
@@ -1677,26 +1682,22 @@ class EC2ContainerServiceBackend(BaseBackend):
 
         return result, failures
 
-    def update_service(
-        self,
-        cluster_str: str,
-        service_str: str,
-        task_definition_str: str,
-        desired_count: Optional[int],
-    ) -> Service:
+    def update_service(self, service_properties: Dict[str, Any]) -> Service:
+        cluster_str = service_properties.pop("cluster", "default")
+        task_definition_str = service_properties.pop("task_definition", None)
         cluster = self._get_cluster(cluster_str)
-
-        service_name = service_str.split("/")[-1]
+        service_name = service_properties.pop("service").split("/")[-1]
         cluster_service_pair = f"{cluster.name}:{service_name}"
+
         if cluster_service_pair in self.services:
-            if task_definition_str is not None:
+            current_service = self.services[cluster_service_pair]
+            for prop_name, prop_val in service_properties.items():
+                if prop_val is not None:
+                    current_service.__setattr__(prop_name, prop_val)
+            if task_definition_str:
                 self.describe_task_definition(task_definition_str)
-                self.services[
-                    cluster_service_pair
-                ].task_definition = task_definition_str
-            if desired_count is not None:
-                self.services[cluster_service_pair].desired_count = desired_count
-            return self.services[cluster_service_pair]
+                current_service.task_definition = task_definition_str
+            return current_service
         else:
             raise ServiceNotFoundException
 
diff --git a/moto/ecs/responses.py b/moto/ecs/responses.py
index dd40d60cf..f388ad16e 100644
--- a/moto/ecs/responses.py
+++ b/moto/ecs/responses.py
@@ -2,6 +2,7 @@ import json
 from typing import Any, Dict
 
 from moto.core.responses import BaseResponse
+from moto.core.utils import camelcase_to_underscores
 
 from .models import EC2ContainerServiceBackend, ecs_backends
 
@@ -306,13 +307,12 @@ class EC2ContainerServiceResponse(BaseResponse):
         return json.dumps(resp)
 
     def update_service(self) -> str:
-        cluster_str = self._get_param("cluster", "default")
-        service_name = self._get_param("service")
-        task_definition = self._get_param("taskDefinition")
-        desired_count = self._get_int_param("desiredCount")
-        service = self.ecs_backend.update_service(
-            cluster_str, service_name, task_definition, desired_count
-        )
+        service_properties = json.loads(self.body)
+        parsed_props = {}
+        for k, v in service_properties.items():
+            parsed_props[camelcase_to_underscores(k)] = v
+
+        service = self.ecs_backend.update_service(parsed_props)
         return json.dumps({"service": service.response_object})
 
     def delete_service(self) -> str:


Test patch:
diff --git a/tests/test_ecs/test_ecs_boto3.py b/tests/test_ecs/test_ecs_boto3.py
index 29f3848d1..dd9295645 100644
--- a/tests/test_ecs/test_ecs_boto3.py
+++ b/tests/test_ecs/test_ecs_boto3.py
@@ -3650,6 +3650,84 @@ def test_list_tasks_with_filters():
     assert len(resp["taskArns"]) == 1
 
 
+@pytest.mark.parametrize(
+    "update_params",
+    [
+        {
+            "loadBalancers": [
+                {
+                    "targetGroupArn": "arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/mock-service-http",
+                    "loadBalancerName": "mock-service1",
+                    "containerName": "mock-container1",
+                    "containerPort": 80,
+                }
+            ]
+        },
+        {
+            "serviceRegistries": [
+                {
+                    "registryArn": "string",
+                    "port": 123,
+                    "containerName": "string",
+                    "containerPort": 123,
+                },
+                {
+                    "registryArn": "string2",
+                    "port": 1234,
+                    "containerName": "string2",
+                    "containerPort": 1234,
+                },
+            ],
+        },
+        {
+            "placementStrategy": [
+                {"type": "random", "field": "string"},
+            ],
+        },
+        {
+            "deploymentConfiguration": {
+                "deploymentCircuitBreaker": {"enable": True, "rollback": True},
+                "maximumPercent": 10,
+                "minimumHealthyPercent": 1,
+                "alarms": {
+                    "alarmNames": [
+                        "string",
+                    ],
+                    "enable": True,
+                    "rollback": True,
+                },
+            },
+        },
+    ],
+)
+@mock_aws
+def test_remove_tg(update_params):
+    # Setup
+    ecs_client = boto3.client("ecs", region_name=ECS_REGION)
+    cluster = "mock-cluster"
+    service = "mock-service"
+    ecs_client.create_cluster(clusterName=cluster)
+    ecs_client.create_service(
+        cluster=cluster,
+        serviceName=service,
+    )
+
+    # Execute
+    ecs_client.update_service(
+        cluster="arn:aws:ecs:us-east-1:123456789012:cluster/" + cluster,
+        service=service,
+        **update_params,
+    )
+    response = ecs_client.describe_services(cluster=cluster, services=[service])
+    response_svc = response["services"][0]
+    property_key = next(iter(update_params))
+    property_val = next(iter(update_params.values()))
+
+    # Verify
+    assert len(response_svc[property_key]) == len(property_val)
+    assert response_svc[property_key] == update_params[property_key]
+
+
 def setup_ecs(client, ec2):
     """test helper"""
     vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16")