Add tag_resource and untag_resource for ECS services

* Refactor resource ARN parsing
This commit is contained in:
Jim Shields 2019-10-03 15:16:07 -04:00
parent d8333fdd7e
commit 6cb1173719
3 changed files with 235 additions and 9 deletions

View File

@ -959,28 +959,31 @@ class EC2ContainerServiceBackend(BaseBackend):
yield task_fam
def list_tags_for_resource(self, resource_arn):
"""Currently only implemented for task definitions"""
@staticmethod
def _parse_resource_arn(resource_arn):
match = re.match(
"^arn:aws:ecs:(?P<region>[^:]+):(?P<account_id>[^:]+):(?P<service>[^:]+)/(?P<id>.*)$",
resource_arn)
if not match:
raise JsonRESTError('InvalidParameterException', 'The ARN provided is invalid.')
return match.groupdict()
service = match.group("service")
if service == "task-definition":
def list_tags_for_resource(self, resource_arn):
"""Currently implemented only for task definitions and services"""
parsed_arn = self._parse_resource_arn(resource_arn)
if parsed_arn["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()
elif service == "service":
for _service in self.services.values():
if _service.arn == resource_arn:
return _service.tags
elif parsed_arn["service"] == "service":
for service in self.services.values():
if service.arn == resource_arn:
return service.tags
else:
raise ServiceNotFoundException(service_name=match.group("id"))
raise ServiceNotFoundException(service_name=parsed_arn["id"])
raise NotImplementedError()
def _get_last_task_definition_revision_id(self, family):
@ -988,6 +991,42 @@ class EC2ContainerServiceBackend(BaseBackend):
if definitions:
return max(definitions.keys())
def tag_resource(self, resource_arn, tags):
"""Currently implemented only for services"""
parsed_arn = self._parse_resource_arn(resource_arn)
if parsed_arn["service"] == "service":
for service in self.services.values():
if service.arn == resource_arn:
service.tags = self._merge_tags(service.tags, tags)
return {}
else:
raise ServiceNotFoundException(service_name=parsed_arn["id"])
raise NotImplementedError()
def _merge_tags(self, existing_tags, new_tags):
merged_tags = new_tags
new_keys = self._get_keys(new_tags)
for existing_tag in existing_tags:
if existing_tag["key"] not in new_keys:
merged_tags.append(existing_tag)
return merged_tags
@staticmethod
def _get_keys(tags):
return [tag['key'] for tag in tags]
def untag_resource(self, resource_arn, tag_keys):
"""Currently implemented only for services"""
parsed_arn = self._parse_resource_arn(resource_arn)
if parsed_arn["service"] == "service":
for service in self.services.values():
if service.arn == resource_arn:
service.tags = [tag for tag in service.tags if tag["key"] not in tag_keys]
return {}
else:
raise ServiceNotFoundException(service_name=parsed_arn["id"])
raise NotImplementedError()
available_regions = boto3.session.Session().get_available_regions("ecs")
ecs_backends = {region: EC2ContainerServiceBackend(region) for region in available_regions}

View File

@ -320,3 +320,15 @@ class EC2ContainerServiceResponse(BaseResponse):
resource_arn = self._get_param('resourceArn')
tags = self.ecs_backend.list_tags_for_resource(resource_arn)
return json.dumps({'tags': tags})
def tag_resource(self):
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):
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)

View File

@ -2411,3 +2411,178 @@ def test_list_tags_for_resource_unknown_service():
client.list_tags_for_resource(resourceArn=service_arn)
except ClientError as err:
err.response['Error']['Code'].should.equal('ServiceNotFoundException')
@mock_ecs
def test_ecs_service_tag_resource():
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'}
}
]
)
response = client.create_service(
cluster='test_ecs_cluster',
serviceName='test_ecs_service',
taskDefinition='test_ecs_task',
desiredCount=2
)
client.tag_resource(
resourceArn=response['service']['serviceArn'],
tags=[
{'key': 'createdBy', 'value': 'moto-unittest'},
{'key': 'foo', 'value': 'bar'},
]
)
response = client.list_tags_for_resource(resourceArn=response['service']['serviceArn'])
type(response['tags']).should.be(list)
response['tags'].should.equal([
{'key': 'createdBy', 'value': 'moto-unittest'},
{'key': 'foo', 'value': 'bar'},
])
@mock_ecs
def test_ecs_service_tag_resource_overwrites_tag():
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'}
}
]
)
response = client.create_service(
cluster='test_ecs_cluster',
serviceName='test_ecs_service',
taskDefinition='test_ecs_task',
desiredCount=2,
tags=[
{'key': 'foo', 'value': 'bar'},
]
)
client.tag_resource(
resourceArn=response['service']['serviceArn'],
tags=[
{'key': 'createdBy', 'value': 'moto-unittest'},
{'key': 'foo', 'value': 'hello world'},
]
)
response = client.list_tags_for_resource(resourceArn=response['service']['serviceArn'])
type(response['tags']).should.be(list)
response['tags'].should.equal([
{'key': 'createdBy', 'value': 'moto-unittest'},
{'key': 'foo', 'value': 'hello world'},
])
@mock_ecs
def test_ecs_service_untag_resource():
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'}
}
]
)
response = client.create_service(
cluster='test_ecs_cluster',
serviceName='test_ecs_service',
taskDefinition='test_ecs_task',
desiredCount=2,
tags=[
{'key': 'foo', 'value': 'bar'},
]
)
client.untag_resource(
resourceArn=response['service']['serviceArn'],
tagKeys=['foo']
)
response = client.list_tags_for_resource(resourceArn=response['service']['serviceArn'])
response['tags'].should.equal([])
@mock_ecs
def test_ecs_service_untag_resource_multiple_tags():
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'}
}
]
)
response = client.create_service(
cluster='test_ecs_cluster',
serviceName='test_ecs_service',
taskDefinition='test_ecs_task',
desiredCount=2,
tags=[
{'key': 'foo', 'value': 'bar'},
{'key': 'createdBy', 'value': 'moto-unittest'},
{'key': 'hello', 'value': 'world'},
]
)
client.untag_resource(
resourceArn=response['service']['serviceArn'],
tagKeys=['foo', 'createdBy']
)
response = client.list_tags_for_resource(resourceArn=response['service']['serviceArn'])
response['tags'].should.equal([
{'key': 'hello', 'value': 'world'},
])