diff --git a/moto/ecs/exceptions.py b/moto/ecs/exceptions.py index bb7e685c8..6e329f227 100644 --- a/moto/ecs/exceptions.py +++ b/moto/ecs/exceptions.py @@ -1,5 +1,5 @@ from __future__ import unicode_literals -from moto.core.exceptions import RESTError +from moto.core.exceptions import RESTError, JsonRESTError class ServiceNotFoundException(RESTError): @@ -11,3 +11,13 @@ class ServiceNotFoundException(RESTError): message="The service {0} does not exist".format(service_name), template='error_json', ) + + +class TaskDefinitionNotFoundException(JsonRESTError): + code = 400 + + def __init__(self): + super(TaskDefinitionNotFoundException, self).__init__( + error_type="ClientException", + message="The specified task definition does not exist.", + ) diff --git a/moto/ecs/models.py b/moto/ecs/models.py index 7f8005a6a..92759651d 100644 --- a/moto/ecs/models.py +++ b/moto/ecs/models.py @@ -1,4 +1,5 @@ from __future__ import unicode_literals +import re import uuid from datetime import datetime from random import random, randint @@ -10,7 +11,10 @@ from moto.core import BaseBackend, BaseModel from moto.ec2 import ec2_backends from copy import copy -from .exceptions import ServiceNotFoundException +from .exceptions import ( + ServiceNotFoundException, + TaskDefinitionNotFoundException +) class BaseObject(BaseModel): @@ -103,12 +107,13 @@ class Cluster(BaseObject): class TaskDefinition(BaseObject): - def __init__(self, family, revision, container_definitions, volumes=None): + def __init__(self, family, revision, container_definitions, volumes=None, tags=None): self.family = family self.revision = revision self.arn = 'arn:aws:ecs:us-east-1:012345678910:task-definition/{0}:{1}'.format( family, revision) self.container_definitions = container_definitions + self.tags = tags if tags is not None else [] if volumes is None: self.volumes = [] else: @@ -119,6 +124,7 @@ class TaskDefinition(BaseObject): response_object = self.gen_response_object() response_object['taskDefinitionArn'] = response_object['arn'] del response_object['arn'] + del response_object['tags'] return response_object @property @@ -464,7 +470,7 @@ class EC2ContainerServiceBackend(BaseBackend): else: raise Exception("{0} is not a cluster".format(cluster_name)) - def register_task_definition(self, family, container_definitions, volumes): + def register_task_definition(self, family, container_definitions, volumes, tags=None): if family in self.task_definitions: last_id = self._get_last_task_definition_revision_id(family) revision = (last_id or 0) + 1 @@ -472,7 +478,7 @@ class EC2ContainerServiceBackend(BaseBackend): self.task_definitions[family] = {} revision = 1 task_definition = TaskDefinition( - family, revision, container_definitions, volumes) + family, revision, container_definitions, volumes, tags) self.task_definitions[family][revision] = task_definition return task_definition @@ -951,6 +957,24 @@ class EC2ContainerServiceBackend(BaseBackend): yield task_fam + def list_tags_for_resource(self, resource_arn): + """Currently only implemented for task definitions""" + match = re.match( + "^arn:aws:ecs:(?P[^:]+):(?P[^:]+):(?P[^:]+)/(?P.*)$", + resource_arn) + if not match: + raise JsonRESTError('InvalidParameterException', 'The ARN provided is invalid.') + + service = match.group("service") + if service == "task-definition": + for task_definition in self.task_definitions.values(): + for revision in task_definition.values(): + if revision.arn == resource_arn: + return revision.tags + else: + raise TaskDefinitionNotFoundException() + raise NotImplementedError() + def _get_last_task_definition_revision_id(self, family): definitions = self.task_definitions.get(family, {}) if definitions: diff --git a/moto/ecs/responses.py b/moto/ecs/responses.py index 92b769fad..abb79ea78 100644 --- a/moto/ecs/responses.py +++ b/moto/ecs/responses.py @@ -62,8 +62,9 @@ class EC2ContainerServiceResponse(BaseResponse): family = self._get_param('family') container_definitions = self._get_param('containerDefinitions') volumes = self._get_param('volumes') + tags = self._get_param('tags') task_definition = self.ecs_backend.register_task_definition( - family, container_definitions, volumes) + family, container_definitions, volumes, tags) return json.dumps({ 'taskDefinition': task_definition.response_object }) @@ -313,3 +314,8 @@ class EC2ContainerServiceResponse(BaseResponse): results = self.ecs_backend.list_task_definition_families(family_prefix, status, max_results, next_token) return json.dumps({'families': list(results)}) + + def list_tags_for_resource(self): + resource_arn = self._get_param('resourceArn') + tags = self.ecs_backend.list_tags_for_resource(resource_arn) + return json.dumps({'tags': tags}) diff --git a/tests/test_ecs/test_ecs_boto3.py b/tests/test_ecs/test_ecs_boto3.py index b147c4159..27f37308e 100644 --- a/tests/test_ecs/test_ecs_boto3.py +++ b/tests/test_ecs/test_ecs_boto3.py @@ -94,6 +94,10 @@ def test_register_task_definition(): }], 'logConfiguration': {'logDriver': 'json-file'} } + ], + tags=[ + {'key': 'createdBy', 'value': 'moto-unittest'}, + {'key': 'foo', 'value': 'bar'}, ] ) type(response['taskDefinition']).should.be(dict) @@ -2304,3 +2308,52 @@ def test_create_service_load_balancing(): response['service']['status'].should.equal('ACTIVE') response['service']['taskDefinition'].should.equal( 'arn:aws:ecs:us-east-1:012345678910:task-definition/test_ecs_task:1') + + +@mock_ecs +def test_list_tags_for_resource(): + client = boto3.client('ecs', region_name='us-east-1') + response = 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'} + } + ], + tags=[ + {'key': 'createdBy', 'value': 'moto-unittest'}, + {'key': 'foo', 'value': 'bar'}, + ] + ) + type(response['taskDefinition']).should.be(dict) + response['taskDefinition']['revision'].should.equal(1) + response['taskDefinition']['taskDefinitionArn'].should.equal( + 'arn:aws:ecs:us-east-1:012345678910:task-definition/test_ecs_task:1') + + task_definition_arn = response['taskDefinition']['taskDefinitionArn'] + response = client.list_tags_for_resource(resourceArn=task_definition_arn) + + type(response['tags']).should.be(list) + response['tags'].should.equal([ + {'key': 'createdBy', 'value': 'moto-unittest'}, + {'key': 'foo', 'value': 'bar'}, + ]) + + +@mock_ecs +def test_list_tags_for_resource_unknown(): + client = boto3.client('ecs', region_name='us-east-1') + task_definition_arn = 'arn:aws:ecs:us-east-1:012345678910:task-definition/unknown:1' + try: + client.list_tags_for_resource(resourceArn=task_definition_arn) + except ClientError as err: + err.response['Error']['Code'].should.equal('ClientException')