Repository: getmoto/moto

Issue:
launchType in ECS create_service
In `moto/ecs/responses.py` the function `create_services` doesn't pickup the launchType parameter.  As a result, the `describe_service` operation doesn't return the launchType passed during creation.


Discussion:
Thanks for raising this @borisreitman - marking it as an enhancement!

Resolving patch:
diff --git a/moto/ecs/models.py b/moto/ecs/models.py
--- a/moto/ecs/models.py
+++ b/moto/ecs/models.py
@@ -256,6 +256,7 @@ def __init__(
         scheduling_strategy=None,
         tags=None,
         deployment_controller=None,
+        launch_type=None,
     ):
         self.cluster_arn = cluster.arn
         self.arn = "arn:aws:ecs:{0}:012345678910:service/{1}".format(
@@ -272,12 +273,14 @@ def __init__(
         self.task_sets = []
         self.deployment_controller = deployment_controller or {"type": "ECS"}
         self.events = []
+        self.launch_type = launch_type
         if self.deployment_controller["type"] == "ECS":
             self.deployments = [
                 {
                     "createdAt": datetime.now(pytz.utc),
                     "desiredCount": self.desired_count,
                     "id": "ecs-svc/{}".format(randint(0, 32 ** 12)),
+                    "launchType": self.launch_type,
                     "pendingCount": self.desired_count,
                     "runningCount": 0,
                     "status": "PRIMARY",
@@ -1011,6 +1014,7 @@ def create_service(
         scheduling_strategy=None,
         tags=None,
         deployment_controller=None,
+        launch_type=None,
     ):
         cluster_name = cluster_str.split("/")[-1]
         if cluster_name in self.clusters:
@@ -1023,6 +1027,12 @@ def create_service(
             task_definition = None
         desired_count = desired_count if desired_count is not None else 0
 
+        launch_type = launch_type if launch_type is not None else "EC2"
+        if launch_type not in ["EC2", "FARGATE"]:
+            raise InvalidParameterException(
+                "launch type should be one of [EC2,FARGATE]"
+            )
+
         service = Service(
             cluster,
             service_name,
@@ -1032,6 +1042,7 @@ def create_service(
             scheduling_strategy,
             tags,
             deployment_controller,
+            launch_type,
         )
         cluster_service_pair = "{0}:{1}".format(cluster_name, service_name)
         self.services[cluster_service_pair] = service
@@ -1478,6 +1489,12 @@ def create_task_set(
         client_token=None,
         tags=None,
     ):
+        launch_type = launch_type if launch_type is not None else "EC2"
+        if launch_type not in ["EC2", "FARGATE"]:
+            raise InvalidParameterException(
+                "launch type should be one of [EC2,FARGATE]"
+            )
+
         task_set = TaskSet(
             service,
             cluster,
diff --git a/moto/ecs/responses.py b/moto/ecs/responses.py
--- a/moto/ecs/responses.py
+++ b/moto/ecs/responses.py
@@ -166,6 +166,7 @@ def create_service(self):
         scheduling_strategy = self._get_param("schedulingStrategy")
         tags = self._get_param("tags")
         deployment_controller = self._get_param("deploymentController")
+        launch_type = self._get_param("launchType")
         service = self.ecs_backend.create_service(
             cluster_str,
             service_name,
@@ -175,6 +176,7 @@ def create_service(self):
             scheduling_strategy,
             tags,
             deployment_controller,
+            launch_type,
         )
         return json.dumps({"service": service.response_object})
 


Test patch:
diff --git a/tests/test_ecs/test_ecs_boto3.py b/tests/test_ecs/test_ecs_boto3.py
--- a/tests/test_ecs/test_ecs_boto3.py
+++ b/tests/test_ecs/test_ecs_boto3.py
@@ -422,6 +422,50 @@ def test_create_service():
         "arn:aws:ecs:us-east-1:012345678910:task-definition/test_ecs_task:1"
     )
     response["service"]["schedulingStrategy"].should.equal("REPLICA")
+    response["service"]["launchType"].should.equal("EC2")
+
+
+@mock_ecs
+def test_create_service_errors():
+    # given
+    client = boto3.client("ecs", region_name="us-east-1")
+    _ = client.create_cluster(clusterName="test_ecs_cluster")
+    _ = client.register_task_definition(
+        family="test_ecs_task",
+        containerDefinitions=[
+            {
+                "name": "hello_world",
+                "image": "docker/hello-world:latest",
+                "cpu": 1024,
+                "memory": 400,
+                "essential": True,
+                "environment": [
+                    {"name": "AWS_ACCESS_KEY_ID", "value": "SOME_ACCESS_KEY"}
+                ],
+                "logConfiguration": {"logDriver": "json-file"},
+            }
+        ],
+    )
+
+    # not existing launch type
+    # when
+    with pytest.raises(ClientError) as e:
+        client.create_service(
+            cluster="test_ecs_cluster",
+            serviceName="test_ecs_service",
+            taskDefinition="test_ecs_task",
+            desiredCount=2,
+            launchType="SOMETHING",
+        )
+
+    # then
+    ex = e.value
+    ex.operation_name.should.equal("CreateService")
+    ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
+    ex.response["Error"]["Code"].should.contain("ClientException")
+    ex.response["Error"]["Message"].should.equal(
+        "launch type should be one of [EC2,FARGATE]"
+    )
 
 
 @mock_ecs
@@ -582,6 +626,7 @@ def test_describe_services():
     response["services"][0]["deployments"][0]["pendingCount"].should.equal(2)
     response["services"][0]["deployments"][0]["runningCount"].should.equal(0)
     response["services"][0]["deployments"][0]["status"].should.equal("PRIMARY")
+    response["services"][0]["deployments"][0]["launchType"].should.equal("EC2")
     (
         datetime.now()
         - response["services"][0]["deployments"][0]["createdAt"].replace(tzinfo=None)
@@ -602,6 +647,8 @@ def test_describe_services():
         [{"key": "Name", "value": "test_ecs_service1"}]
     )
     response["services"][1]["tags"].should.equal([])
+    response["services"][0]["launchType"].should.equal("EC2")
+    response["services"][1]["launchType"].should.equal("EC2")
 
 
 @mock_ecs
@@ -2617,16 +2664,70 @@ def test_create_task_set():
     service_arn = client.describe_services(
         cluster=cluster_name, services=[service_name]
     )["services"][0]["serviceArn"]
-    assert task_set["clusterArn"] == cluster_arn
-    assert task_set["serviceArn"] == service_arn
-    assert task_set["taskDefinition"].endswith("{0}:1".format(task_def_name))
-    assert task_set["scale"] == {"value": 100.0, "unit": "PERCENT"}
-    assert (
-        task_set["loadBalancers"][0]["targetGroupArn"]
-        == "arn:aws:elasticloadbalancing:us-east-1:01234567890:targetgroup/c26b93c1bc35466ba792d5b08fe6a5bc/ec39113f8831453a"
+    task_set["clusterArn"].should.equal(cluster_arn)
+    task_set["serviceArn"].should.equal(service_arn)
+    task_set["taskDefinition"].should.match("{0}:1$".format(task_def_name))
+    task_set["scale"].should.equal({"value": 100.0, "unit": "PERCENT"})
+    task_set["loadBalancers"][0]["targetGroupArn"].should.equal(
+        "arn:aws:elasticloadbalancing:us-east-1:01234567890:targetgroup/"
+        "c26b93c1bc35466ba792d5b08fe6a5bc/ec39113f8831453a"
+    )
+    task_set["loadBalancers"][0]["containerPort"].should.equal(8080)
+    task_set["loadBalancers"][0]["containerName"].should.equal("hello_world")
+    task_set["launchType"].should.equal("EC2")
+
+
+@mock_ecs
+def test_create_task_set_errors():
+    # given
+    cluster_name = "test_ecs_cluster"
+    service_name = "test_ecs_service"
+    task_def_name = "test_ecs_task"
+
+    client = boto3.client("ecs", region_name="us-east-1")
+    _ = client.create_cluster(clusterName=cluster_name)
+    _ = client.register_task_definition(
+        family="test_ecs_task",
+        containerDefinitions=[
+            {
+                "name": "hello_world",
+                "image": "docker/hello-world:latest",
+                "cpu": 1024,
+                "memory": 400,
+                "essential": True,
+                "environment": [
+                    {"name": "AWS_ACCESS_KEY_ID", "value": "SOME_ACCESS_KEY"}
+                ],
+                "logConfiguration": {"logDriver": "json-file"},
+            }
+        ],
+    )
+    _ = client.create_service(
+        cluster=cluster_name,
+        serviceName=service_name,
+        taskDefinition=task_def_name,
+        desiredCount=2,
+        deploymentController={"type": "EXTERNAL"},
+    )
+
+    # not existing launch type
+    # when
+    with pytest.raises(ClientError) as e:
+        client.create_task_set(
+            cluster=cluster_name,
+            service=service_name,
+            taskDefinition=task_def_name,
+            launchType="SOMETHING",
+        )
+
+    # then
+    ex = e.value
+    ex.operation_name.should.equal("CreateTaskSet")
+    ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
+    ex.response["Error"]["Code"].should.contain("ClientException")
+    ex.response["Error"]["Message"].should.equal(
+        "launch type should be one of [EC2,FARGATE]"
     )
-    assert task_set["loadBalancers"][0]["containerPort"] == 8080
-    assert task_set["loadBalancers"][0]["containerName"] == "hello_world"
 
 
 @mock_ecs
@@ -2692,20 +2793,21 @@ def test_describe_task_sets():
         cluster=cluster_name, services=[service_name]
     )["services"][0]["serviceArn"]
 
-    assert "tags" in task_sets[0]
-    assert len(task_sets) == 1
-    assert task_sets[0]["taskDefinition"].endswith("{0}:1".format(task_def_name))
-    assert task_sets[0]["clusterArn"] == cluster_arn
-    assert task_sets[0]["serviceArn"] == service_arn
-    assert task_sets[0]["serviceArn"].endswith(service_name)
-    assert task_sets[0]["scale"] == {"value": 100.0, "unit": "PERCENT"}
-    assert task_sets[0]["taskSetArn"].endswith(task_sets[0]["id"])
-    assert (
-        task_sets[0]["loadBalancers"][0]["targetGroupArn"]
-        == "arn:aws:elasticloadbalancing:us-east-1:01234567890:targetgroup/c26b93c1bc35466ba792d5b08fe6a5bc/ec39113f8831453a"
-    )
-    assert task_sets[0]["loadBalancers"][0]["containerPort"] == 8080
-    assert task_sets[0]["loadBalancers"][0]["containerName"] == "hello_world"
+    task_sets[0].should.have.key("tags")
+    task_sets.should.have.length_of(1)
+    task_sets[0]["taskDefinition"].should.match("{0}:1$".format(task_def_name))
+    task_sets[0]["clusterArn"].should.equal(cluster_arn)
+    task_sets[0]["serviceArn"].should.equal(service_arn)
+    task_sets[0]["serviceArn"].should.match("{0}$".format(service_name))
+    task_sets[0]["scale"].should.equal({"value": 100.0, "unit": "PERCENT"})
+    task_sets[0]["taskSetArn"].should.match("{0}$".format(task_sets[0]["id"]))
+    task_sets[0]["loadBalancers"][0]["targetGroupArn"].should.equal(
+        "arn:aws:elasticloadbalancing:us-east-1:01234567890:targetgroup/"
+        "c26b93c1bc35466ba792d5b08fe6a5bc/ec39113f8831453a"
+    )
+    task_sets[0]["loadBalancers"][0]["containerPort"].should.equal(8080)
+    task_sets[0]["loadBalancers"][0]["containerName"].should.equal("hello_world")
+    task_sets[0]["launchType"].should.equal("EC2")
 
 
 @mock_ecs