diff --git a/moto/ecs/models.py b/moto/ecs/models.py index 7d3ac156e..e0efb3414 100644 --- a/moto/ecs/models.py +++ b/moto/ecs/models.py @@ -126,32 +126,72 @@ class TaskDefinition(BaseObject, CloudFormationModel): volumes=None, tags=None, placement_constraints=None, + requires_compatibilities=None, + cpu=None, + memory=None, ): self.family = family self.revision = revision self.arn = "arn:aws:ecs:{0}:012345678910:task-definition/{1}:{2}".format( region_name, family, revision ) - self.container_definitions = container_definitions + + default_container_definition = { + "cpu": 0, + "portMappings": [], + "essential": True, + "environment": [], + "mountPoints": [], + "volumesFrom": [], + } + self.container_definitions = [] + for container_definition in container_definitions: + full_definition = default_container_definition.copy() + full_definition.update(container_definition) + self.container_definitions.append(full_definition) + self.tags = tags if tags is not None else [] + if volumes is None: self.volumes = [] else: self.volumes = volumes - if network_mode is None: + + if not requires_compatibilities or requires_compatibilities == ["EC2"]: + self.compatibilities = ["EC2"] + else: + self.compatibilities = ["EC2", "FARGATE"] + + if network_mode is None and "FARGATE" not in self.compatibilities: self.network_mode = "bridge" + elif "FARGATE" in self.compatibilities: + self.network_mode = "awsvpc" else: self.network_mode = network_mode + self.placement_constraints = ( placement_constraints if placement_constraints is not None else [] ) + self.requires_compatibilities = requires_compatibilities + + self.cpu = cpu + self.memory = memory + @property def response_object(self): response_object = self.gen_response_object() response_object["taskDefinitionArn"] = response_object["arn"] del response_object["arn"] del response_object["tags"] + + if not response_object["requiresCompatibilities"]: + del response_object["requiresCompatibilities"] + if not response_object["cpu"]: + del response_object["cpu"] + if not response_object["memory"]: + del response_object["memory"] + return response_object @property @@ -683,6 +723,9 @@ class EC2ContainerServiceBackend(BaseBackend): network_mode=None, tags=None, placement_constraints=None, + requires_compatibilities=None, + cpu=None, + memory=None, ): if family in self.task_definitions: last_id = self._get_last_task_definition_revision_id(family) @@ -699,6 +742,9 @@ class EC2ContainerServiceBackend(BaseBackend): network_mode=network_mode, tags=tags, placement_constraints=placement_constraints, + requires_compatibilities=requires_compatibilities, + cpu=cpu, + memory=memory, ) self.task_definitions[family][revision] = task_definition diff --git a/moto/ecs/responses.py b/moto/ecs/responses.py index 03acc44f7..9e6cb182e 100644 --- a/moto/ecs/responses.py +++ b/moto/ecs/responses.py @@ -64,6 +64,9 @@ class EC2ContainerServiceResponse(BaseResponse): tags = self._get_param("tags") network_mode = self._get_param("networkMode") placement_constraints = self._get_param("placementConstraints") + requires_compatibilities = self._get_param("requiresCompatibilities") + cpu = self._get_param("cpu") + memory = self._get_param("memory") task_definition = self.ecs_backend.register_task_definition( family, container_definitions, @@ -71,6 +74,9 @@ class EC2ContainerServiceResponse(BaseResponse): network_mode=network_mode, tags=tags, placement_constraints=placement_constraints, + requires_compatibilities=requires_compatibilities, + cpu=cpu, + memory=memory, ) return json.dumps({"taskDefinition": task_definition.response_object}) diff --git a/tests/test_ecs/test_ecs_boto3.py b/tests/test_ecs/test_ecs_boto3.py index 1887bf89e..997023466 100644 --- a/tests/test_ecs/test_ecs_boto3.py +++ b/tests/test_ecs/test_ecs_boto3.py @@ -91,53 +91,113 @@ def test_delete_cluster_exceptions(): @mock_ecs def test_register_task_definition(): client = boto3.client("ecs", region_name="us-east-1") - response = client.register_task_definition( + # Registering with minimal definition + definition = dict( 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"}, - } - ], - networkMode="bridge", - tags=[ - {"key": "createdBy", "value": "moto-unittest"}, - {"key": "foo", "value": "bar"}, + {"name": "hello_world", "image": "hello-world:latest", "memory": 400,} ], ) - type(response["taskDefinition"]).should.be(dict) + + response = client.register_task_definition(**definition) + + response["taskDefinition"] = response["taskDefinition"] + response["taskDefinition"]["family"].should.equal("test_ecs_task") response["taskDefinition"]["revision"].should.equal(1) response["taskDefinition"]["taskDefinitionArn"].should.equal( "arn:aws:ecs:us-east-1:012345678910:task-definition/test_ecs_task:1" ) + response["taskDefinition"]["networkMode"].should.equal("bridge") + response["taskDefinition"]["volumes"].should.equal([]) + response["taskDefinition"]["placementConstraints"].should.equal([]) + response["taskDefinition"]["compatibilities"].should.equal(["EC2"]) + response["taskDefinition"].shouldnt.have.key("requiresCompatibilities") + response["taskDefinition"].shouldnt.have.key("cpu") + response["taskDefinition"].shouldnt.have.key("memory") + response["taskDefinition"]["containerDefinitions"][0]["name"].should.equal( "hello_world" ) response["taskDefinition"]["containerDefinitions"][0]["image"].should.equal( - "docker/hello-world:latest" + "hello-world:latest" + ) + response["taskDefinition"]["containerDefinitions"][0]["cpu"].should.equal(0) + response["taskDefinition"]["containerDefinitions"][0]["portMappings"].should.equal( + [] ) - response["taskDefinition"]["containerDefinitions"][0]["cpu"].should.equal(1024) - response["taskDefinition"]["containerDefinitions"][0]["memory"].should.equal(400) response["taskDefinition"]["containerDefinitions"][0]["essential"].should.equal( True ) + response["taskDefinition"]["containerDefinitions"][0]["environment"].should.equal( + [] + ) + response["taskDefinition"]["containerDefinitions"][0]["mountPoints"].should.equal( + [] + ) + response["taskDefinition"]["containerDefinitions"][0]["volumesFrom"].should.equal( + [] + ) + + # Registering again increments the revision + response = client.register_task_definition(**definition) + + response["taskDefinition"]["revision"].should.equal(2) + response["taskDefinition"]["taskDefinitionArn"].should.equal( + "arn:aws:ecs:us-east-1:012345678910:task-definition/test_ecs_task:2" + ) + + # Registering with optional top-level params + definition["requiresCompatibilities"] = ["FARGATE"] + response = client.register_task_definition(**definition) + response["taskDefinition"]["requiresCompatibilities"].should.equal(["FARGATE"]) + response["taskDefinition"]["compatibilities"].should.equal(["EC2", "FARGATE"]) + response["taskDefinition"]["networkMode"].should.equal("awsvpc") + + definition["requiresCompatibilities"] = ["EC2", "FARGATE"] + response = client.register_task_definition(**definition) + response["taskDefinition"]["requiresCompatibilities"].should.equal( + ["EC2", "FARGATE"] + ) + response["taskDefinition"]["compatibilities"].should.equal(["EC2", "FARGATE"]) + response["taskDefinition"]["networkMode"].should.equal("awsvpc") + + definition["cpu"] = "512" + response = client.register_task_definition(**definition) + response["taskDefinition"]["cpu"].should.equal("512") + + definition.update({"memory": "512"}) + response = client.register_task_definition(**definition) + response["taskDefinition"]["memory"].should.equal("512") + + # Registering with optional container params + definition["containerDefinitions"][0]["cpu"] = 512 + response = client.register_task_definition(**definition) + response["taskDefinition"]["containerDefinitions"][0]["cpu"].should.equal(512) + + definition["containerDefinitions"][0]["essential"] = False + response = client.register_task_definition(**definition) + response["taskDefinition"]["containerDefinitions"][0]["essential"].should.equal( + False + ) + + definition["containerDefinitions"][0]["environment"] = [ + {"name": "AWS_ACCESS_KEY_ID", "value": "SOME_ACCESS_KEY"} + ] + response = client.register_task_definition(**definition) response["taskDefinition"]["containerDefinitions"][0]["environment"][0][ "name" ].should.equal("AWS_ACCESS_KEY_ID") response["taskDefinition"]["containerDefinitions"][0]["environment"][0][ "value" ].should.equal("SOME_ACCESS_KEY") + + definition["containerDefinitions"][0]["logConfiguration"] = { + "logDriver": "json-file" + } + response = client.register_task_definition(**definition) response["taskDefinition"]["containerDefinitions"][0]["logConfiguration"][ "logDriver" ].should.equal("json-file") - response["taskDefinition"]["networkMode"].should.equal("bridge") @mock_ecs diff --git a/tests/test_ecs/test_ecs_cloudformation.py b/tests/test_ecs/test_ecs_cloudformation.py index fcb1beec7..526f88d15 100644 --- a/tests/test_ecs/test_ecs_cloudformation.py +++ b/tests/test_ecs/test_ecs_cloudformation.py @@ -269,6 +269,5 @@ def test_create_task_definition_through_cloudformation(): template["Resources"]["testTaskDefinition"]["Properties"], pascal_to_camelcase ) task_definition["volumes"].should.equal(expected_properties["volumes"]) - task_definition["containerDefinitions"].should.equal( - expected_properties["containerDefinitions"] - ) + for key, value in expected_properties["containerDefinitions"][0].items(): + task_definition["containerDefinitions"][0][key].should.equal(value)