From 7f387b0bb9842d3561f59ed7fa70b92e17791909 Mon Sep 17 00:00:00 2001 From: Niels Laukens Date: Wed, 4 Sep 2019 16:56:06 +0200 Subject: [PATCH] Add elasticbeanstalk Tags handling --- moto/eb/exceptions.py | 6 +++ moto/eb/models.py | 47 +++++++++++++++++++-- moto/eb/responses.py | 88 +++++++++++++++++++++++++++++++++++++--- tests/test_eb/test_eb.py | 72 ++++++++++++++++++++++++++++++++ 4 files changed, 203 insertions(+), 10 deletions(-) diff --git a/moto/eb/exceptions.py b/moto/eb/exceptions.py index c470d5317..bf3a89618 100644 --- a/moto/eb/exceptions.py +++ b/moto/eb/exceptions.py @@ -5,3 +5,9 @@ class InvalidParameterValueError(RESTError): def __init__(self, message): super(InvalidParameterValueError, self).__init__( "InvalidParameterValue", message) + + +class ResourceNotFoundException(RESTError): + def __init__(self, message): + super(ResourceNotFoundException, self).__init__( + "ResourceNotFoundException", message) diff --git a/moto/eb/models.py b/moto/eb/models.py index 5b4655175..c3c2aa20c 100644 --- a/moto/eb/models.py +++ b/moto/eb/models.py @@ -7,32 +7,70 @@ from .exceptions import InvalidParameterValueError class FakeEnvironment(BaseModel): - def __init__(self, application, environment_name): - self.environment_name = environment_name + def __init__( + self, + application, + environment_name, + tags, + ): self.application = weakref.proxy(application) # weakref to break circular dependencies + self.environment_name = environment_name + self.tags = tags @property def application_name(self): return self.application.application_name + @property + def environment_arn(self): + return 'arn:aws:elasticbeanstalk:{region}:{account_id}:' \ + 'environment/{application_name}/{environment_name}'.format( + region=self.region, + account_id='123456789012', + application_name=self.application_name, + environment_name=self.environment_name, + ) + + @property + def platform_arn(self): + return 'TODO' # TODO + + @property + def solution_stack_name(self): + return 'TODO' # TODO + + @property + def region(self): + return self.application.region + class FakeApplication(BaseModel): - def __init__(self, application_name): + def __init__(self, backend, application_name): + self.backend = weakref.proxy(backend) # weakref to break cycles self.application_name = application_name self.environments = dict() - def create_environment(self, environment_name): + def create_environment( + self, + environment_name, + tags, + ): if environment_name in self.environments: raise InvalidParameterValueError env = FakeEnvironment( application=self, environment_name=environment_name, + tags=tags, ) self.environments[environment_name] = env return env + @property + def region(self): + return self.backend.region + class EBBackend(BaseBackend): def __init__(self, region): @@ -52,6 +90,7 @@ class EBBackend(BaseBackend): "Application {} already exists.".format(application_name) ) new_app = FakeApplication( + backend=self, application_name=application_name, ) self.applications[application_name] = new_app diff --git a/moto/eb/responses.py b/moto/eb/responses.py index fecdb8c21..fbace1938 100644 --- a/moto/eb/responses.py +++ b/moto/eb/responses.py @@ -1,6 +1,7 @@ from moto.core.responses import BaseResponse +from moto.core.utils import tags_from_query_string from .models import eb_backends, EBBackend -from .exceptions import InvalidParameterValueError +from .exceptions import InvalidParameterValueError, ResourceNotFoundException class EBResponse(BaseResponse): @@ -38,7 +39,11 @@ class EBResponse(BaseResponse): "No Application named \'{}\' found.".format(application_name) ) - env = app.create_environment(environment_name=environment_name) + tags = tags_from_query_string(self.querystring, prefix="Tags.member") + env = app.create_environment( + environment_name=environment_name, + tags=tags, + ) template = self.response_template(EB_CREATE_ENVIRONMENT) return template.render( @@ -62,6 +67,48 @@ class EBResponse(BaseResponse): def list_available_solution_stacks(): return EB_LIST_AVAILABLE_SOLUTION_STACKS + def _find_environment_by_arn(self, arn): + for app in self.backend.applications.keys(): + for env in self.backend.applications[app].environments.values(): + if env.environment_arn == arn: + return env + raise KeyError() + + def update_tags_for_resource(self): + resource_arn = self._get_param('ResourceArn') + try: + res = self._find_environment_by_arn(resource_arn) + except KeyError: + raise ResourceNotFoundException( + "Resource not found for ARN \'{}\'.".format(resource_arn) + ) + + tags_to_add = tags_from_query_string(self.querystring, prefix="TagsToAdd.member") + for key, value in tags_to_add.items(): + res.tags[key] = value + + tags_to_remove = self._get_multi_param('TagsToRemove.member') + for key in tags_to_remove: + del res.tags[key] + + return EB_UPDATE_TAGS_FOR_RESOURCE + + def list_tags_for_resource(self): + resource_arn = self._get_param('ResourceArn') + try: + res = self._find_environment_by_arn(resource_arn) + except KeyError: + raise ResourceNotFoundException( + "Resource not found for ARN \'{}\'.".format(resource_arn) + ) + tags = res.tags + + template = self.response_template(EB_LIST_TAGS_FOR_RESOURCE) + return template.render( + tags=tags, + arn=resource_arn, + ) + EB_CREATE_APPLICATION = """ @@ -136,11 +183,11 @@ EB_CREATE_ENVIRONMENT = """ {{ environment.solution_stack_name }} Grey - arn:aws:elasticbeanstalk:{{ region }}:111122223333:environment/{{ environment.application_name }}/{{ environment.environment_name }} + {{ environment.environment_arn }} 2019-09-04T09:41:24.222Z 2019-09-04T09:41:24.222Z {{ environment_id }} - arn:aws:elasticbeanstalk:{{ region }}::platform/{{ environment.platform_arn }} + {{ environment.platform_arn }} WebServer Standard @@ -165,7 +212,7 @@ EB_DESCRIBE_ENVIRONMENTS = """ {{ env.solution_stack_name }} Grey - arn:aws:elasticbeanstalk:{{ region }}:123456789012:environment/{{ env.application_name }}/{{ env.environment_name }} + {{ env.environment_arn }} false 2019-08-30T09:35:10.913Z false @@ -173,7 +220,7 @@ EB_DESCRIBE_ENVIRONMENTS = """ 2019-08-22T07:02:47.332Z {{ env.environment_id }} 1 - arn:aws:elasticbeanstalk:{{ region }}::platform/{{ env.platform_arn }} + {{ env.platform_arn }} WebServer Standard @@ -1347,3 +1394,32 @@ EB_LIST_AVAILABLE_SOLUTION_STACKS = """ """ + + +EB_UPDATE_TAGS_FOR_RESOURCE = """ + + + f355d788-e67e-440f-b915-99e35254ffee + + +""" + + +EB_LIST_TAGS_FOR_RESOURCE = """ + + + + {% for key, value in tags.items() %} + + {{ key }} + {{ value }} + + {% endfor %} + + {{ arn }} + + + 178e410f-3b57-456f-a64c-a3b6a16da9ab + + +""" diff --git a/tests/test_eb/test_eb.py b/tests/test_eb/test_eb.py index aafe524fd..2b5be4490 100644 --- a/tests/test_eb/test_eb.py +++ b/tests/test_eb/test_eb.py @@ -72,6 +72,78 @@ def test_describe_environments(): envs[0]['EnvironmentName'].should.equal('myenv') +def tags_dict_to_list(tag_dict): + tag_list = [] + for key, value in tag_dict.items(): + tag_list.append({'Key': key, 'Value': value}) + return tag_list + + +def tags_list_to_dict(tag_list): + tag_dict = {} + for tag in tag_list: + tag_dict[tag['Key']] = tag['Value'] + return tag_dict + + +@mock_eb +def test_create_environment_tags(): + conn = boto3.client('elasticbeanstalk', region_name='us-east-1') + conn.create_application( + ApplicationName="myapp", + ) + env_tags = {'initial key': 'initial value'} + env = conn.create_environment( + ApplicationName="myapp", + EnvironmentName="myenv", + Tags=tags_dict_to_list(env_tags), + ) + + tags = conn.list_tags_for_resource( + ResourceArn=env['EnvironmentArn'], + ) + tags['ResourceArn'].should.equal(env['EnvironmentArn']) + tags_list_to_dict(tags['ResourceTags']).should.equal(env_tags) + + +@mock_eb +def test_update_tags(): + conn = boto3.client('elasticbeanstalk', region_name='us-east-1') + conn.create_application( + ApplicationName="myapp", + ) + env_tags = { + 'initial key': 'initial value', + 'to remove': 'delete me', + 'to update': 'original', + } + env = conn.create_environment( + ApplicationName="myapp", + EnvironmentName="myenv", + Tags=tags_dict_to_list(env_tags), + ) + + extra_env_tags = { + 'to update': 'new', + 'extra key': 'extra value', + } + conn.update_tags_for_resource( + ResourceArn=env['EnvironmentArn'], + TagsToAdd=tags_dict_to_list(extra_env_tags), + TagsToRemove=['to remove'], + ) + + total_env_tags = env_tags.copy() + total_env_tags.update(extra_env_tags) + del total_env_tags['to remove'] + + tags = conn.list_tags_for_resource( + ResourceArn=env['EnvironmentArn'], + ) + tags['ResourceArn'].should.equal(env['EnvironmentArn']) + tags_list_to_dict(tags['ResourceTags']).should.equal(total_env_tags) + + @mock_eb def test_list_available_solution_stacks(): conn = boto3.client('elasticbeanstalk', region_name='us-east-1')