More accurately mock ECS RegisterTaskDefinition (#3584)

The mocked response for ECS RegisterTaskDefinition has drifted from what
actually returns when run against a real ECS endpoint. I created a
minimal task definition for both EC2:

```
>>> ecs.register_task_definition(
      family="moto",
      containerDefinitions=[
          {
              "name": "hello_world",
              "image": "hello-world:latest",
              "memory": 400
           }
       ]
  )["taskDefinition"]

{'taskDefinitionArn': 'arn:aws:ecs:us-east-1:************:task-definition/moto:1',
 'containerDefinitions': [{'name': 'hello_world',
   'image': 'hello-world:latest',
   'cpu': 0,
   'memory': 400,
   'portMappings': [],
   'essential': True,
   'environment': [],
   'mountPoints': [],
   'volumesFrom': []}],
 'family': 'moto',
 'revision': 1,
 'volumes': [],
 'status': 'ACTIVE',
 'placementConstraints': [],
 'compatibilities': ['EC2']}
```

and FARGATE:
```
>>> ecs.register_task_definition(
      family="moto",
      containerDefinitions=[
          {
              "name": "hello_world",
              "image": "hello-world:latest",
              "memory": 400
           }
       ],
       requiresCompatibilities=["FARGATE"],
       networkMode="awsvpc",
       cpu="256",
       memory="512"
  )["taskDefinition"]

{'taskDefinitionArn': 'arn:aws:ecs:us-east-1:************:task-definition/moto:2',
 'containerDefinitions': [{'name': 'hello_world',
   'image': 'hello-world:latest',
   'cpu': 0,
   'memory': 400,
   'portMappings': [],
   'essential': True,
   'environment': [],
   'mountPoints': [],
   'volumesFrom': []}],
 'family': 'moto',
 'networkMode': 'awsvpc',
 'revision': 2,
 'volumes': [],
 'status': 'ACTIVE',
 'requiresAttributes': [{'name': 'com.amazonaws.ecs.capability.docker-remote-api.1.18'},
  {'name': 'ecs.capability.task-eni'}],
 'placementConstraints': [],
 'compatibilities': ['EC2', 'FARGATE'],
 'requiresCompatibilities': ['FARGATE'],
 'cpu': '256',
 'memory': '512'}
```

This change adds several default keys to the task based on those two
real responses and the AWS documentation:
https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_RegisterTaskDefinition.html

The mock still doesn't match the real response exactly but it's much
closer than it was before.
This commit is contained in:
Jordan Sanders 2021-01-09 08:07:35 -06:00 committed by GitHub
parent d2b751e22d
commit 6dfd64ff3c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 138 additions and 27 deletions

View File

@ -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

View File

@ -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})

View File

@ -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

View File

@ -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)