moto/moto/ecs/responses.py

445 lines
17 KiB
Python
Raw Normal View History

from __future__ import unicode_literals
import json
from moto.core.responses import BaseResponse
from .models import ecs_backends
class EC2ContainerServiceResponse(BaseResponse):
@property
def ecs_backend(self):
2017-10-28 19:18:39 +01:00
"""
ECS Backend
:return: ECS Backend object
:rtype: moto.ecs.models.EC2ContainerServiceBackend
"""
return ecs_backends[self.region]
@property
def request_params(self):
try:
2017-02-16 22:51:04 -05:00
return json.loads(self.body)
except ValueError:
return {}
lambda + SNS enhancements (#1048) * updates - support lambda messages from SNS - run lambda in docker container * decode output * populate timeout * simplify * whoops * skeletons of cloudwatchlogs * impl filter log streams * fix logging * PEP fixes * PEP fixes * fix reset * fix reset * add new endpoint * fix region name * add docker * try to fix tests * try to fix travis issue with boto * fix escaping in urls * fix environment variables * fix PEP * more pep * switch back to precise * another fix attempt * fix typo * fix lambda invoke * fix more unittests * work on getting this to work in new scheme * fix py2 * fix error * fix tests when running in server mode * more lambda fixes * try running with latest docker adapted from aiodocker * switch to docker python client * pep fixes * switch to docker volume * fix unittest * fix invoke from sns * fix zip2tar * add hack impl for get_function with zip * try fix * fix for py < 3.6 * add volume refcount * try to fix travis * docker test * fix yaml * try fix * update endpoints * fix * another attempt * try again * fix recursive import * refactor fix * revert changes with better fix * more reverts * wait for service to come up * add back detached mode * sleep and add another exception type * put this back for logging * put back with note * whoops :) * docker in docker! * fix invalid url * hopefully last fix! * fix lambda regions * fix protocol * travis!!!! * just run lambda test for now * use one print * fix escaping * another attempt * yet another * re-enable all tests * fixes * fix for py2 * revert change * fix for py2.7 * fix output ordering * remove this given there's a new unittest that covers it * changes based on review - add skeleton logs test file - switch to docker image that matches test env - fix mock_logs import * add readme entry
2017-09-27 16:04:58 -07:00
def _get_param(self, param, if_none=None):
return self.request_params.get(param, if_none)
def create_cluster(self):
2019-10-31 08:44:26 -07:00
cluster_name = self._get_param("clusterName")
if cluster_name is None:
2019-10-31 08:44:26 -07:00
cluster_name = "default"
cluster = self.ecs_backend.create_cluster(cluster_name)
2019-10-31 08:44:26 -07:00
return json.dumps({"cluster": cluster.response_object})
def list_clusters(self):
cluster_arns = self.ecs_backend.list_clusters()
2019-10-31 08:44:26 -07:00
return json.dumps(
{
"clusterArns": cluster_arns
# 'nextToken': str(uuid.uuid4())
}
)
def describe_clusters(self):
2019-10-31 08:44:26 -07:00
list_clusters_name = self._get_param("clusters")
clusters, failures = self.ecs_backend.describe_clusters(list_clusters_name)
2019-10-31 08:44:26 -07:00
return json.dumps(
{
"clusters": clusters,
"failures": [cluster.response_object for cluster in failures],
}
)
def delete_cluster(self):
2019-10-31 08:44:26 -07:00
cluster_str = self._get_param("cluster")
cluster = self.ecs_backend.delete_cluster(cluster_str)
2019-10-31 08:44:26 -07:00
return json.dumps({"cluster": cluster.response_object})
def register_task_definition(self):
2019-10-31 08:44:26 -07:00
family = self._get_param("family")
container_definitions = self._get_param("containerDefinitions")
volumes = self._get_param("volumes")
tags = self._get_param("tags")
2020-01-09 23:45:14 -06:00
network_mode = self._get_param("networkMode")
2020-05-02 18:47:59 +02:00
placement_constraints = self._get_param("placementConstraints")
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.
2021-01-09 08:07:35 -06:00
requires_compatibilities = self._get_param("requiresCompatibilities")
cpu = self._get_param("cpu")
memory = self._get_param("memory")
2017-02-23 21:37:43 -05:00
task_definition = self.ecs_backend.register_task_definition(
2020-01-09 23:45:14 -06:00
family,
container_definitions,
volumes=volumes,
network_mode=network_mode,
tags=tags,
2020-05-02 18:47:59 +02:00
placement_constraints=placement_constraints,
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.
2021-01-09 08:07:35 -06:00
requires_compatibilities=requires_compatibilities,
cpu=cpu,
memory=memory,
2019-10-31 08:44:26 -07:00
)
return json.dumps({"taskDefinition": task_definition.response_object})
def list_task_definitions(self):
family_prefix = self._get_param("familyPrefix")
task_definition_arns = self.ecs_backend.list_task_definitions(family_prefix)
2019-10-31 08:44:26 -07:00
return json.dumps(
{
"taskDefinitionArns": task_definition_arns
# 'nextToken': str(uuid.uuid4())
}
)
def describe_task_definition(self):
2019-10-31 08:44:26 -07:00
task_definition_str = self._get_param("taskDefinition")
2017-02-23 21:37:43 -05:00
data = self.ecs_backend.describe_task_definition(task_definition_str)
resp = {"taskDefinition": data.response_object, "failures": []}
if "TAGS" in self._get_param("include", []):
resp["tags"] = self.ecs_backend.list_tags_for_resource(data.arn)
return json.dumps(resp)
def deregister_task_definition(self):
2019-10-31 08:44:26 -07:00
task_definition_str = self._get_param("taskDefinition")
2017-02-23 21:37:43 -05:00
task_definition = self.ecs_backend.deregister_task_definition(
2019-10-31 08:44:26 -07:00
task_definition_str
)
return json.dumps({"taskDefinition": task_definition.response_object})
2016-08-30 00:26:13 +01:00
def run_task(self):
cluster_str = self._get_param("cluster", "default")
2019-10-31 08:44:26 -07:00
overrides = self._get_param("overrides")
task_definition_str = self._get_param("taskDefinition")
count = self._get_int_param("count")
started_by = self._get_param("startedBy")
2017-02-23 21:37:43 -05:00
tasks = self.ecs_backend.run_task(
2019-10-31 08:44:26 -07:00
cluster_str, task_definition_str, count, overrides, started_by
)
return json.dumps(
{"tasks": [task.response_object for task in tasks], "failures": []}
)
2016-08-30 00:26:13 +01:00
def describe_tasks(self):
cluster = self._get_param("cluster", "default")
2019-10-31 08:44:26 -07:00
tasks = self._get_param("tasks")
2016-08-30 00:26:13 +01:00
data = self.ecs_backend.describe_tasks(cluster, tasks)
2019-10-31 08:44:26 -07:00
return json.dumps(
{"tasks": [task.response_object for task in data], "failures": []}
)
2016-08-30 00:26:13 +01:00
def start_task(self):
cluster_str = self._get_param("cluster", "default")
2019-10-31 08:44:26 -07:00
overrides = self._get_param("overrides")
task_definition_str = self._get_param("taskDefinition")
container_instances = self._get_param("containerInstances")
started_by = self._get_param("startedBy")
2017-02-23 21:37:43 -05:00
tasks = self.ecs_backend.start_task(
2019-10-31 08:44:26 -07:00
cluster_str, task_definition_str, container_instances, overrides, started_by
)
return json.dumps(
{"tasks": [task.response_object for task in tasks], "failures": []}
)
2016-08-30 00:26:13 +01:00
def list_tasks(self):
cluster_str = self._get_param("cluster", "default")
2019-10-31 08:44:26 -07:00
container_instance = self._get_param("containerInstance")
family = self._get_param("family")
started_by = self._get_param("startedBy")
service_name = self._get_param("serviceName")
desiredStatus = self._get_param("desiredStatus")
2017-02-23 21:37:43 -05:00
task_arns = self.ecs_backend.list_tasks(
2019-10-31 08:44:26 -07:00
cluster_str,
container_instance,
family,
started_by,
service_name,
desiredStatus,
)
return json.dumps({"taskArns": task_arns})
2016-08-30 00:26:13 +01:00
def stop_task(self):
cluster_str = self._get_param("cluster", "default")
2019-10-31 08:44:26 -07:00
task = self._get_param("task")
reason = self._get_param("reason")
2016-08-30 00:26:13 +01:00
task = self.ecs_backend.stop_task(cluster_str, task, reason)
2019-10-31 08:44:26 -07:00
return json.dumps({"task": task.response_object})
2016-08-30 00:26:13 +01:00
def create_service(self):
cluster_str = self._get_param("cluster", "default")
2019-10-31 08:44:26 -07:00
service_name = self._get_param("serviceName")
task_definition_str = self._get_param("taskDefinition")
desired_count = self._get_int_param("desiredCount")
load_balancers = self._get_param("loadBalancers")
scheduling_strategy = self._get_param("schedulingStrategy")
tags = self._get_param("tags")
deployment_controller = self._get_param("deploymentController")
launch_type = self._get_param("launchType")
2017-02-23 21:37:43 -05:00
service = self.ecs_backend.create_service(
2019-10-31 08:44:26 -07:00
cluster_str,
service_name,
desired_count,
task_definition_str,
2019-10-31 08:44:26 -07:00
load_balancers,
scheduling_strategy,
tags,
deployment_controller,
launch_type,
2019-10-31 08:44:26 -07:00
)
return json.dumps({"service": service.response_object})
def list_services(self):
cluster_str = self._get_param("cluster", "default")
2019-10-31 08:44:26 -07:00
scheduling_strategy = self._get_param("schedulingStrategy")
service_arns = self.ecs_backend.list_services(cluster_str, scheduling_strategy)
2019-10-31 08:44:26 -07:00
return json.dumps(
{
"serviceArns": service_arns
# ,
# 'nextToken': str(uuid.uuid4())
}
)
def describe_services(self):
cluster_str = self._get_param("cluster", "default")
2019-10-31 08:44:26 -07:00
service_names = self._get_param("services")
services, failures = self.ecs_backend.describe_services(
cluster_str, service_names
)
resp = {
"services": [service.response_object for service in services],
"failures": failures,
}
if "TAGS" in self._get_param("include", []):
for i, service in enumerate(services):
resp["services"][i]["tags"] = self.ecs_backend.list_tags_for_resource(
service.arn
)
return json.dumps(resp)
def update_service(self):
cluster_str = self._get_param("cluster", "default")
2019-10-31 08:44:26 -07:00
service_name = self._get_param("service")
task_definition = self._get_param("taskDefinition")
desired_count = self._get_int_param("desiredCount")
2017-02-23 21:37:43 -05:00
service = self.ecs_backend.update_service(
2019-10-31 08:44:26 -07:00
cluster_str, service_name, task_definition, desired_count
)
return json.dumps({"service": service.response_object})
def delete_service(self):
2019-10-31 08:44:26 -07:00
service_name = self._get_param("service")
cluster_name = self._get_param("cluster", "default")
force = self._get_param("force", False)
service = self.ecs_backend.delete_service(cluster_name, service_name, force)
2019-10-31 08:44:26 -07:00
return json.dumps({"service": service.response_object})
def register_container_instance(self):
cluster_str = self._get_param("cluster", "default")
2019-10-31 08:44:26 -07:00
instance_identity_document_str = self._get_param("instanceIdentityDocument")
instance_identity_document = json.loads(instance_identity_document_str)
ec2_instance_id = instance_identity_document["instanceId"]
2017-02-23 21:37:43 -05:00
container_instance = self.ecs_backend.register_container_instance(
2019-10-31 08:44:26 -07:00
cluster_str, ec2_instance_id
)
return json.dumps({"containerInstance": container_instance.response_object})
def deregister_container_instance(self):
cluster_str = self._get_param("cluster", "default")
2019-10-31 08:44:26 -07:00
container_instance_str = self._get_param("containerInstance")
force = self._get_param("force")
container_instance, failures = self.ecs_backend.deregister_container_instance(
cluster_str, container_instance_str, force
)
2019-10-31 08:44:26 -07:00
return json.dumps({"containerInstance": container_instance.response_object})
def list_container_instances(self):
cluster_str = self._get_param("cluster", "default")
2019-10-31 08:44:26 -07:00
container_instance_arns = self.ecs_backend.list_container_instances(cluster_str)
return json.dumps({"containerInstanceArns": container_instance_arns})
2016-06-14 17:58:11 +02:00
def describe_container_instances(self):
cluster_str = self._get_param("cluster", "default")
2019-10-31 08:44:26 -07:00
list_container_instance_arns = self._get_param("containerInstances")
2017-02-23 21:37:43 -05:00
container_instances, failures = self.ecs_backend.describe_container_instances(
2019-10-31 08:44:26 -07:00
cluster_str, list_container_instance_arns
)
return json.dumps(
{
"failures": [ci.response_object for ci in failures],
"containerInstances": [
ci.response_object for ci in container_instances
],
}
)
def update_container_instances_state(self):
cluster_str = self._get_param("cluster", "default")
2019-10-31 08:44:26 -07:00
list_container_instance_arns = self._get_param("containerInstances")
status_str = self._get_param("status")
(
container_instances,
failures,
) = self.ecs_backend.update_container_instances_state(
cluster_str, list_container_instance_arns, status_str
)
return json.dumps(
{
"failures": [ci.response_object for ci in failures],
"containerInstances": [
ci.response_object for ci in container_instances
],
}
)
2017-10-28 19:18:39 +01:00
def put_attributes(self):
2019-10-31 08:44:26 -07:00
cluster_name = self._get_param("cluster")
attributes = self._get_param("attributes")
2017-10-28 19:18:39 +01:00
self.ecs_backend.put_attributes(cluster_name, attributes)
2019-10-31 08:44:26 -07:00
return json.dumps({"attributes": attributes})
2017-10-28 19:18:39 +01:00
def list_attributes(self):
2019-10-31 08:44:26 -07:00
cluster_name = self._get_param("cluster")
attr_name = self._get_param("attributeName")
attr_value = self._get_param("attributeValue")
target_type = self._get_param("targetType")
max_results = self._get_param("maxResults")
next_token = self._get_param("nextToken")
results = self.ecs_backend.list_attributes(
target_type, cluster_name, attr_name, attr_value, max_results, next_token
)
2017-10-28 19:18:39 +01:00
# Result will be [item will be {0 cluster_name, 1 arn, 2 name, 3 value}]
formatted_results = []
for _, arn, name, value in results:
2019-10-31 08:44:26 -07:00
tmp_result = {"name": name, "targetId": arn}
2017-10-28 19:18:39 +01:00
if value is not None:
2019-10-31 08:44:26 -07:00
tmp_result["value"] = value
2017-10-28 19:18:39 +01:00
formatted_results.append(tmp_result)
2019-10-31 08:44:26 -07:00
return json.dumps({"attributes": formatted_results})
2017-10-28 19:18:39 +01:00
def delete_attributes(self):
cluster_name = self._get_param("cluster", "default")
2019-10-31 08:44:26 -07:00
attributes = self._get_param("attributes")
2017-10-28 19:18:39 +01:00
self.ecs_backend.delete_attributes(cluster_name, attributes)
2019-10-31 08:44:26 -07:00
return json.dumps({"attributes": attributes})
2017-10-28 19:18:39 +01:00
def discover_poll_endpoint(self):
# Here are the arguments, this api is used by the ecs client so obviously no decent
# documentation. Hence I've responded with valid but useless data
# cluster_name = self._get_param('cluster')
# instance = self._get_param('containerInstance')
2019-10-31 08:44:26 -07:00
return json.dumps(
{"endpoint": "http://localhost", "telemetryEndpoint": "http://localhost"}
)
2017-10-28 19:18:39 +01:00
def list_task_definition_families(self):
2019-10-31 08:44:26 -07:00
family_prefix = self._get_param("familyPrefix")
status = self._get_param("status")
max_results = self._get_param("maxResults")
next_token = self._get_param("nextToken")
2017-10-28 19:18:39 +01:00
2019-10-31 08:44:26 -07:00
results = self.ecs_backend.list_task_definition_families(
family_prefix, status, max_results, next_token
)
2017-10-28 19:18:39 +01:00
2019-10-31 08:44:26 -07:00
return json.dumps({"families": list(results)})
def list_tags_for_resource(self):
2019-10-31 08:44:26 -07:00
resource_arn = self._get_param("resourceArn")
tags = self.ecs_backend.list_tags_for_resource(resource_arn)
2019-10-31 08:44:26 -07:00
return json.dumps({"tags": tags})
def tag_resource(self):
2019-10-31 08:44:26 -07:00
resource_arn = self._get_param("resourceArn")
tags = self._get_param("tags")
results = self.ecs_backend.tag_resource(resource_arn, tags)
return json.dumps(results)
def untag_resource(self):
2019-10-31 08:44:26 -07:00
resource_arn = self._get_param("resourceArn")
tag_keys = self._get_param("tagKeys")
results = self.ecs_backend.untag_resource(resource_arn, tag_keys)
return json.dumps(results)
def create_task_set(self):
service_str = self._get_param("service")
cluster_str = self._get_param("cluster", "default")
task_definition = self._get_param("taskDefinition")
external_id = self._get_param("externalId")
network_configuration = self._get_param("networkConfiguration")
load_balancers = self._get_param("loadBalancers")
service_registries = self._get_param("serviceRegistries")
launch_type = self._get_param("launchType")
capacity_provider_strategy = self._get_param("capacityProviderStrategy")
platform_version = self._get_param("platformVersion")
scale = self._get_param("scale")
client_token = self._get_param("clientToken")
tags = self._get_param("tags")
task_set = self.ecs_backend.create_task_set(
service_str,
cluster_str,
task_definition,
external_id=external_id,
network_configuration=network_configuration,
load_balancers=load_balancers,
service_registries=service_registries,
launch_type=launch_type,
capacity_provider_strategy=capacity_provider_strategy,
platform_version=platform_version,
scale=scale,
client_token=client_token,
tags=tags,
)
return json.dumps({"taskSet": task_set.response_object})
def describe_task_sets(self):
cluster_str = self._get_param("cluster", "default")
service_str = self._get_param("service")
task_sets = self._get_param("taskSets")
include = self._get_param("include", [])
task_set_objs = self.ecs_backend.describe_task_sets(
cluster_str, service_str, task_sets, include
)
response_objs = [t.response_object for t in task_set_objs]
if "TAGS" not in include:
for ro in response_objs:
del ro["tags"]
return json.dumps({"taskSets": response_objs})
def delete_task_set(self):
cluster_str = self._get_param("cluster")
service_str = self._get_param("service")
task_set = self._get_param("taskSet")
force = self._get_param("force")
task_set = self.ecs_backend.delete_task_set(
cluster_str, service_str, task_set, force
)
return json.dumps({"taskSet": task_set.response_object})
def update_task_set(self):
cluster_str = self._get_param("cluster", "default")
service_str = self._get_param("service")
task_set = self._get_param("taskSet")
scale = self._get_param("scale")
task_set = self.ecs_backend.update_task_set(
cluster_str, service_str, task_set, scale
)
return json.dumps({"taskSet": task_set.response_object})
def update_service_primary_task_set(self):
cluster_str = self._get_param("cluster", "default")
service_str = self._get_param("service")
primary_task_set = self._get_param("primaryTaskSet")
task_set = self.ecs_backend.update_service_primary_task_set(
cluster_str, service_str, primary_task_set
)
return json.dumps({"taskSet": task_set.response_object})