From c95d472bf5ef9746521cf54c83bb61333c3eafcd Mon Sep 17 00:00:00 2001 From: Niels Laukens Date: Tue, 3 Sep 2019 14:54:46 +0200 Subject: [PATCH 01/12] Add (failing) test for ElasticBeanstalk --- tests/test_eb/test_eb.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 tests/test_eb/test_eb.py diff --git a/tests/test_eb/test_eb.py b/tests/test_eb/test_eb.py new file mode 100644 index 00000000..924ed3ad --- /dev/null +++ b/tests/test_eb/test_eb.py @@ -0,0 +1,15 @@ +import boto3 +from moto import mock_eb + + +@mock_eb +def test_application(): + # Create Elastic Beanstalk Application + eb_client = boto3.client('elasticbeanstalk', region_name='us-east-1') + + eb_client.create_application( + ApplicationName="myapp", + ) + + eb_apps = eb_client.describe_applications() + eb_apps['Applications'][0]['ApplicationName'].should.equal("myapp") From 336f50349af0eddbb5d82776258abd9f847fa989 Mon Sep 17 00:00:00 2001 From: Niels Laukens Date: Tue, 3 Sep 2019 16:10:32 +0200 Subject: [PATCH 02/12] Add sub-minimal mocking of elasticbeanstalk:create_application() --- moto/__init__.py | 1 + moto/eb/__init__.py | 4 ++ moto/eb/exceptions.py | 7 +++ moto/eb/models.py | 37 ++++++++++++++++ moto/eb/responses.py | 92 ++++++++++++++++++++++++++++++++++++++++ moto/eb/urls.py | 11 +++++ tests/test_eb/test_eb.py | 34 ++++++++++++--- 7 files changed, 181 insertions(+), 5 deletions(-) create mode 100644 moto/eb/__init__.py create mode 100644 moto/eb/exceptions.py create mode 100644 moto/eb/models.py create mode 100644 moto/eb/responses.py create mode 100644 moto/eb/urls.py diff --git a/moto/__init__.py b/moto/__init__.py index 8594cedd..7cb6d0e3 100644 --- a/moto/__init__.py +++ b/moto/__init__.py @@ -18,6 +18,7 @@ from .datapipeline import mock_datapipeline, mock_datapipeline_deprecated # fla from .dynamodb import mock_dynamodb, mock_dynamodb_deprecated # flake8: noqa from .dynamodb2 import mock_dynamodb2, mock_dynamodb2_deprecated # flake8: noqa from .dynamodbstreams import mock_dynamodbstreams # flake8: noqa +from .eb import mock_eb # flake8: noqa from .ec2 import mock_ec2, mock_ec2_deprecated # flake8: noqa from .ecr import mock_ecr, mock_ecr_deprecated # flake8: noqa from .ecs import mock_ecs, mock_ecs_deprecated # flake8: noqa diff --git a/moto/eb/__init__.py b/moto/eb/__init__.py new file mode 100644 index 00000000..3e06e959 --- /dev/null +++ b/moto/eb/__init__.py @@ -0,0 +1,4 @@ +from .models import eb_backends +from moto.core.models import base_decorator + +mock_eb = base_decorator(eb_backends) diff --git a/moto/eb/exceptions.py b/moto/eb/exceptions.py new file mode 100644 index 00000000..c470d531 --- /dev/null +++ b/moto/eb/exceptions.py @@ -0,0 +1,7 @@ +from moto.core.exceptions import RESTError + + +class InvalidParameterValueError(RESTError): + def __init__(self, message): + super(InvalidParameterValueError, self).__init__( + "InvalidParameterValue", message) diff --git a/moto/eb/models.py b/moto/eb/models.py new file mode 100644 index 00000000..246d33cd --- /dev/null +++ b/moto/eb/models.py @@ -0,0 +1,37 @@ +import boto.beanstalk + +from moto.core import BaseBackend, BaseModel +from .exceptions import InvalidParameterValueError + + +class FakeApplication(BaseModel): + def __init__(self, application_name): + self.application_name = application_name + + +class EBBackend(BaseBackend): + def __init__(self, region): + self.region = region + self.applications = dict() + + def reset(self): + # preserve region + region = self.region + self._reset_model_refs() + self.__dict__ = {} + self.__init__(region) + + def create_application(self, application_name): + if application_name in self.applications: + raise InvalidParameterValueError( + "Application {} already exists.".format(application_name) + ) + new_app = FakeApplication( + application_name=application_name, + ) + self.applications[application_name] = new_app + return new_app + + +eb_backends = dict((region.name, EBBackend(region.name)) + for region in boto.beanstalk.regions()) diff --git a/moto/eb/responses.py b/moto/eb/responses.py new file mode 100644 index 00000000..9cf8b2e4 --- /dev/null +++ b/moto/eb/responses.py @@ -0,0 +1,92 @@ +from moto.core.responses import BaseResponse +from .models import eb_backends + +EB_CREATE_APPLICATION = """ + + + + + 2019-09-03T13:08:29.049Z + + + + false + 180 + false + + + false + 200 + false + + + + arn:aws:elasticbeanstalk:{{ region_name }}:111122223333:application/{{ application_name }} + {{ application.application_name }} + 2019-09-03T13:08:29.049Z + + + + 1b6173c8-13aa-4b0a-99e9-eb36a1fb2778 + + +""" + + +EB_DESCRIBE_APPLICATIONS = """ + + + + {% for application in applications %} + + + 2019-09-03T13:08:29.049Z + + + + 180 + false + false + + + false + 200 + false + + + + arn:aws:elasticbeanstalk:{{ region_name }}:387323646340:application/{{ application.name }} + {{ application.application_name }} + 2019-09-03T13:08:29.049Z + + {% endfor %} + + + + 015a05eb-282e-4b76-bd18-663fdfaf42e4 + + +""" + + +class EBResponse(BaseResponse): + @property + def backend(self): + return eb_backends[self.region] + + def create_application(self): + app = self.backend.create_application( + application_name=self._get_param('ApplicationName'), + ) + + template = self.response_template(EB_CREATE_APPLICATION) + return template.render( + region_name=self.backend.region, + application=app, + ) + + def describe_applications(self): + template = self.response_template(EB_DESCRIBE_APPLICATIONS) + return template.render( + applications=self.backend.applications.values(), + ) diff --git a/moto/eb/urls.py b/moto/eb/urls.py new file mode 100644 index 00000000..4cd4add1 --- /dev/null +++ b/moto/eb/urls.py @@ -0,0 +1,11 @@ +from __future__ import unicode_literals + +from .responses import EBResponse + +url_bases = [ + r"https?://elasticbeanstalk.(?P[a-zA-Z0-9\-_]+).amazonaws.com", +] + +url_paths = { + '{0}/$': EBResponse.dispatch, +} diff --git a/tests/test_eb/test_eb.py b/tests/test_eb/test_eb.py index 924ed3ad..9e863e7f 100644 --- a/tests/test_eb/test_eb.py +++ b/tests/test_eb/test_eb.py @@ -1,15 +1,39 @@ import boto3 +import sure # noqa +from botocore.exceptions import ClientError + from moto import mock_eb @mock_eb -def test_application(): +def test_create_application(): # Create Elastic Beanstalk Application - eb_client = boto3.client('elasticbeanstalk', region_name='us-east-1') + conn = boto3.client('elasticbeanstalk', region_name='us-east-1') + app = conn.create_application( + ApplicationName="myapp", + ) + app['Application']['ApplicationName'].should.equal("myapp") - eb_client.create_application( + +@mock_eb +def test_create_application_dup(): + conn = boto3.client('elasticbeanstalk', region_name='us-east-1') + conn.create_application( + ApplicationName="myapp", + ) + conn.create_application.when.called_with( + ApplicationName="myapp", + ).should.throw(ClientError) + + +@mock_eb +def test_describe_applications(): + # Create Elastic Beanstalk Application + conn = boto3.client('elasticbeanstalk', region_name='us-east-1') + conn.create_application( ApplicationName="myapp", ) - eb_apps = eb_client.describe_applications() - eb_apps['Applications'][0]['ApplicationName'].should.equal("myapp") + apps = conn.describe_applications() + len(apps['Applications']).should.equal(1) + apps['Applications'][0]['ApplicationName'].should.equal('myapp') From 6f23a39fc26c3cf51b4f0e2b49277be85024d666 Mon Sep 17 00:00:00 2001 From: Niels Laukens Date: Wed, 4 Sep 2019 15:33:15 +0200 Subject: [PATCH 03/12] Add minimal mocking of elasticbeanstalk:create_environment, describe_environments and list_available_solution_stacks --- moto/eb/models.py | 25 + moto/eb/responses.py | 1297 +++++++++++++++++++++++++++++++++++++- tests/test_eb/test_eb.py | 41 ++ 3 files changed, 1343 insertions(+), 20 deletions(-) diff --git a/moto/eb/models.py b/moto/eb/models.py index 246d33cd..5b465517 100644 --- a/moto/eb/models.py +++ b/moto/eb/models.py @@ -1,12 +1,37 @@ +import weakref + import boto.beanstalk from moto.core import BaseBackend, BaseModel from .exceptions import InvalidParameterValueError +class FakeEnvironment(BaseModel): + def __init__(self, application, environment_name): + self.environment_name = environment_name + self.application = weakref.proxy(application) # weakref to break circular dependencies + + @property + def application_name(self): + return self.application.application_name + + class FakeApplication(BaseModel): def __init__(self, application_name): self.application_name = application_name + self.environments = dict() + + def create_environment(self, environment_name): + if environment_name in self.environments: + raise InvalidParameterValueError + + env = FakeEnvironment( + application=self, + environment_name=environment_name, + ) + self.environments[environment_name] = env + + return env class EBBackend(BaseBackend): diff --git a/moto/eb/responses.py b/moto/eb/responses.py index 9cf8b2e4..fecdb8c2 100644 --- a/moto/eb/responses.py +++ b/moto/eb/responses.py @@ -1,5 +1,67 @@ from moto.core.responses import BaseResponse -from .models import eb_backends +from .models import eb_backends, EBBackend +from .exceptions import InvalidParameterValueError + + +class EBResponse(BaseResponse): + @property + def backend(self): + """ + :rtype: EBBackend + """ + return eb_backends[self.region] + + def create_application(self): + app = self.backend.create_application( + application_name=self._get_param('ApplicationName'), + ) + + template = self.response_template(EB_CREATE_APPLICATION) + return template.render( + region_name=self.backend.region, + application=app, + ) + + def describe_applications(self): + template = self.response_template(EB_DESCRIBE_APPLICATIONS) + return template.render( + applications=self.backend.applications.values(), + ) + + def create_environment(self): + application_name = self._get_param('ApplicationName') + environment_name = self._get_param('EnvironmentName') + try: + app = self.backend.applications[application_name] + except KeyError: + raise InvalidParameterValueError( + "No Application named \'{}\' found.".format(application_name) + ) + + env = app.create_environment(environment_name=environment_name) + + template = self.response_template(EB_CREATE_ENVIRONMENT) + return template.render( + environment=env, + region=self.backend.region, + ) + + def describe_environments(self): + envs = [] + + for app in self.backend.applications.values(): + for env in app.environments.values(): + envs.append(env) + + template = self.response_template(EB_DESCRIBE_ENVIRONMENTS) + return template.render( + environments=envs, + ) + + @staticmethod + def list_available_solution_stacks(): + return EB_LIST_AVAILABLE_SOLUTION_STACKS + EB_CREATE_APPLICATION = """ @@ -55,7 +117,7 @@ EB_DESCRIBE_APPLICATIONS = """ - arn:aws:elasticbeanstalk:{{ region_name }}:387323646340:application/{{ application.name }} + arn:aws:elasticbeanstalk:{{ region_name }}:111122223333:application/{{ application.name }} {{ application.application_name }} 2019-09-03T13:08:29.049Z @@ -69,24 +131,1219 @@ EB_DESCRIBE_APPLICATIONS = """ """ -class EBResponse(BaseResponse): - @property - def backend(self): - return eb_backends[self.region] +EB_CREATE_ENVIRONMENT = """ + + + {{ environment.solution_stack_name }} + Grey + arn:aws:elasticbeanstalk:{{ region }}:111122223333:environment/{{ environment.application_name }}/{{ environment.environment_name }} + 2019-09-04T09:41:24.222Z + 2019-09-04T09:41:24.222Z + {{ environment_id }} + arn:aws:elasticbeanstalk:{{ region }}::platform/{{ environment.platform_arn }} + + WebServer + Standard + 1.0 + + {{ environment.environment_name }} + {{ environment.application_name }} + Launching + + + 18dc8158-f5d7-4d5a-82ef-07fcaadf81c6 + + +""" - def create_application(self): - app = self.backend.create_application( - application_name=self._get_param('ApplicationName'), - ) - template = self.response_template(EB_CREATE_APPLICATION) - return template.render( - region_name=self.backend.region, - application=app, - ) +EB_DESCRIBE_ENVIRONMENTS = """ + + + + {% for env in environments %} + + {{ env.solution_stack_name }} + Grey + arn:aws:elasticbeanstalk:{{ region }}:123456789012:environment/{{ env.application_name }}/{{ env.environment_name }} + false + 2019-08-30T09:35:10.913Z + false + + 2019-08-22T07:02:47.332Z + {{ env.environment_id }} + 1 + arn:aws:elasticbeanstalk:{{ region }}::platform/{{ env.platform_arn }} + + WebServer + Standard + 1.0 + + No Data + {{ env.environment_name }} + + + + {{ env.application_name }} + Ready + + {% endfor %} + + + + dd56b215-01a0-40b2-bd1e-57589c39424f + + +""" - def describe_applications(self): - template = self.response_template(EB_DESCRIBE_APPLICATIONS) - return template.render( - applications=self.backend.applications.values(), - ) + +# Current list as of 2019-09-04 +EB_LIST_AVAILABLE_SOLUTION_STACKS = """ + + + + 64bit Amazon Linux 2018.03 v4.10.1 running Node.js + 64bit Amazon Linux 2018.03 v4.9.2 running Node.js + 64bit Amazon Linux 2018.03 v4.8.0 running Node.js + 64bit Amazon Linux 2018.03 v4.6.0 running Node.js + 64bit Amazon Linux 2018.03 v4.5.3 running Node.js + 64bit Amazon Linux 2018.03 v4.5.1 running Node.js + 64bit Amazon Linux 2018.03 v4.5.0 running Node.js + 64bit Amazon Linux 2017.09 v4.4.6 running Node.js + 64bit Amazon Linux 2017.09 v4.4.5 running Node.js + 64bit Amazon Linux 2017.09 v4.4.4 running Node.js + 64bit Amazon Linux 2017.09 v4.4.2 running Node.js + 64bit Amazon Linux 2017.09 v4.4.0 running Node.js + 64bit Amazon Linux 2017.03 v4.3.0 running Node.js + 64bit Amazon Linux 2017.03 v4.2.2 running Node.js + 64bit Amazon Linux 2017.03 v4.2.1 running Node.js + 64bit Amazon Linux 2017.03 v4.2.0 running Node.js + 64bit Amazon Linux 2017.03 v4.1.1 running Node.js + 64bit Amazon Linux 2017.03 v4.1.0 running Node.js + 64bit Amazon Linux 2016.09 v4.0.1 running Node.js + 64bit Amazon Linux 2016.09 v4.0.0 running Node.js + 64bit Amazon Linux 2016.09 v3.3.1 running Node.js + 64bit Amazon Linux 2016.09 v3.1.0 running Node.js + 64bit Amazon Linux 2018.03 v2.8.14 running PHP 5.4 + 64bit Amazon Linux 2018.03 v2.8.14 running PHP 5.5 + 64bit Amazon Linux 2018.03 v2.8.14 running PHP 5.6 + 64bit Amazon Linux 2018.03 v2.8.14 running PHP 7.0 + 64bit Amazon Linux 2018.03 v2.8.14 running PHP 7.1 + 64bit Amazon Linux 2018.03 v2.8.14 running PHP 7.2 + 64bit Amazon Linux 2018.03 v2.8.12 running PHP 7.2 + 64bit Amazon Linux 2018.03 v2.8.7 running PHP 7.1 + 64bit Amazon Linux 2018.03 v2.8.6 running PHP 7.1 + 64bit Amazon Linux 2018.03 v2.8.6 running PHP 7.2 + 64bit Amazon Linux 2018.03 v2.8.5 running PHP 7.2 + 64bit Amazon Linux 2018.03 v2.8.4 running PHP 7.2 + 64bit Amazon Linux 2018.03 v2.8.3 running PHP 7.2 + 64bit Amazon Linux 2018.03 v2.8.2 running PHP 7.2 + 64bit Amazon Linux 2018.03 v2.8.1 running PHP 7.2 + 64bit Amazon Linux 2018.03 v2.8.0 running PHP 7.1 + 64bit Amazon Linux 2018.03 v2.7.1 running PHP 5.6 + 64bit Amazon Linux 2018.03 v2.7.1 running PHP 7.0 + 64bit Amazon Linux 2018.03 v2.7.1 running PHP 7.1 + 64bit Amazon Linux 2018.03 v2.7.0 running PHP 7.0 + 64bit Amazon Linux 2018.03 v2.7.0 running PHP 7.1 + 64bit Amazon Linux 2017.09 v2.6.6 running PHP 5.4 + 64bit Amazon Linux 2017.09 v2.6.6 running PHP 5.6 + 64bit Amazon Linux 2017.09 v2.6.6 running PHP 7.0 + 64bit Amazon Linux 2017.09 v2.6.5 running PHP 7.0 + 64bit Amazon Linux 2017.09 v2.6.4 running PHP 5.4 + 64bit Amazon Linux 2017.09 v2.6.4 running PHP 5.5 + 64bit Amazon Linux 2017.09 v2.6.4 running PHP 5.6 + 64bit Amazon Linux 2017.09 v2.6.4 running PHP 7.0 + 64bit Amazon Linux 2017.09 v2.6.4 running PHP 7.1 + 64bit Amazon Linux 2017.09 v2.6.3 running PHP 5.4 + 64bit Amazon Linux 2017.09 v2.6.3 running PHP 5.5 + 64bit Amazon Linux 2017.09 v2.6.3 running PHP 5.6 + 64bit Amazon Linux 2017.09 v2.6.3 running PHP 7.0 + 64bit Amazon Linux 2017.09 v2.6.3 running PHP 7.1 + 64bit Amazon Linux 2017.09 v2.6.2 running PHP 5.6 + 64bit Amazon Linux 2017.09 v2.6.2 running PHP 7.0 + 64bit Amazon Linux 2017.09 v2.6.1 running PHP 7.0 + 64bit Amazon Linux 2017.09 v2.6.0 running PHP 5.4 + 64bit Amazon Linux 2017.09 v2.6.0 running PHP 5.5 + 64bit Amazon Linux 2017.09 v2.6.0 running PHP 5.6 + 64bit Amazon Linux 2017.09 v2.6.0 running PHP 7.0 + 64bit Amazon Linux 2017.09 v2.6.0 running PHP 7.1 + 64bit Amazon Linux 2017.03 v2.5.0 running PHP 7.0 + 64bit Amazon Linux 2017.03 v2.5.0 running PHP 7.1 + 64bit Amazon Linux 2017.03 v2.4.4 running PHP 5.5 + 64bit Amazon Linux 2017.03 v2.4.4 running PHP 5.6 + 64bit Amazon Linux 2017.03 v2.4.4 running PHP 7.0 + 64bit Amazon Linux 2017.03 v2.4.3 running PHP 7.0 + 64bit Amazon Linux 2017.03 v2.4.2 running PHP 5.4 + 64bit Amazon Linux 2017.03 v2.4.2 running PHP 5.5 + 64bit Amazon Linux 2017.03 v2.4.2 running PHP 5.6 + 64bit Amazon Linux 2017.03 v2.4.2 running PHP 7.0 + 64bit Amazon Linux 2017.03 v2.4.1 running PHP 7.0 + 64bit Amazon Linux 2017.03 v2.4.0 running PHP 7.0 + 64bit Amazon Linux 2016.09 v2.3.2 running PHP 7.0 + 64bit Amazon Linux 2016.09 v2.3.1 running PHP 7.0 + 64bit Amazon Linux 2018.03 v2.9.1 running Python 3.6 + 64bit Amazon Linux 2018.03 v2.9.1 running Python 3.4 + 64bit Amazon Linux 2018.03 v2.9.1 running Python + 64bit Amazon Linux 2018.03 v2.9.1 running Python 2.7 + 64bit Amazon Linux 2018.03 v2.7.5 running Python 3.6 + 64bit Amazon Linux 2018.03 v2.7.1 running Python 3.6 + 64bit Amazon Linux 2018.03 v2.7.0 running Python 3.6 + 64bit Amazon Linux 2017.09 v2.6.4 running Python 3.6 + 64bit Amazon Linux 2017.09 v2.6.1 running Python 3.6 + 64bit Amazon Linux 2017.03 v2.4.0 running Python 3.4 + 64bit Amazon Linux 2018.03 v2.10.1 running Ruby 2.6 (Puma) + 64bit Amazon Linux 2018.03 v2.10.1 running Ruby 2.5 (Puma) + 64bit Amazon Linux 2018.03 v2.10.1 running Ruby 2.4 (Puma) + 64bit Amazon Linux 2018.03 v2.10.1 running Ruby 2.3 (Puma) + 64bit Amazon Linux 2018.03 v2.10.1 running Ruby 2.2 (Puma) + 64bit Amazon Linux 2018.03 v2.10.1 running Ruby 2.1 (Puma) + 64bit Amazon Linux 2018.03 v2.10.1 running Ruby 2.0 (Puma) + 64bit Amazon Linux 2018.03 v2.10.1 running Ruby 2.6 (Passenger Standalone) + 64bit Amazon Linux 2018.03 v2.10.1 running Ruby 2.5 (Passenger Standalone) + 64bit Amazon Linux 2018.03 v2.10.1 running Ruby 2.4 (Passenger Standalone) + 64bit Amazon Linux 2018.03 v2.10.1 running Ruby 2.3 (Passenger Standalone) + 64bit Amazon Linux 2018.03 v2.10.1 running Ruby 2.2 (Passenger Standalone) + 64bit Amazon Linux 2018.03 v2.10.1 running Ruby 2.1 (Passenger Standalone) + 64bit Amazon Linux 2018.03 v2.10.1 running Ruby 2.0 (Passenger Standalone) + 64bit Amazon Linux 2018.03 v2.10.1 running Ruby 1.9.3 + 64bit Amazon Linux 2018.03 v2.8.0 running Ruby 2.5 (Passenger Standalone) + 64bit Amazon Linux 2017.03 v2.4.4 running Ruby 2.3 (Puma) + 64bit Amazon Linux 2017.03 v2.4.4 running Ruby 2.3 (Passenger Standalone) + 64bit Amazon Linux 2018.03 v3.2.1 running Tomcat 8.5 Java 8 + 64bit Amazon Linux 2018.03 v3.2.1 running Tomcat 8 Java 8 + 64bit Amazon Linux 2018.03 v3.2.1 running Tomcat 7 Java 7 + 64bit Amazon Linux 2018.03 v3.2.1 running Tomcat 7 Java 6 + 64bit Amazon Linux 2018.03 v3.1.1 running Tomcat 8.5 Java 8 + 64bit Amazon Linux 2017.03 v2.6.5 running Tomcat 8 Java 8 + 64bit Amazon Linux 2017.03 v2.6.2 running Tomcat 8 Java 8 + 64bit Amazon Linux 2017.03 v2.6.1 running Tomcat 8 Java 8 + 64bit Amazon Linux 2017.03 v2.6.0 running Tomcat 8 Java 8 + 64bit Amazon Linux 2016.09 v2.5.4 running Tomcat 8 Java 8 + 64bit Amazon Linux 2016.03 v2.1.0 running Tomcat 8 Java 8 + 64bit Windows Server Core 2016 v2.2.1 running IIS 10.0 + 64bit Windows Server 2016 v2.2.1 running IIS 10.0 + 64bit Windows Server Core 2012 R2 v2.2.1 running IIS 8.5 + 64bit Windows Server 2012 R2 v2.2.1 running IIS 8.5 + 64bit Windows Server Core 2016 v1.2.0 running IIS 10.0 + 64bit Windows Server 2016 v1.2.0 running IIS 10.0 + 64bit Windows Server Core 2012 R2 v1.2.0 running IIS 8.5 + 64bit Windows Server 2012 R2 v1.2.0 running IIS 8.5 + 64bit Windows Server 2012 v1.2.0 running IIS 8 + 64bit Windows Server 2008 R2 v1.2.0 running IIS 7.5 + 64bit Windows Server Core 2012 R2 running IIS 8.5 + 64bit Windows Server 2012 R2 running IIS 8.5 + 64bit Windows Server 2012 running IIS 8 + 64bit Windows Server 2008 R2 running IIS 7.5 + 64bit Amazon Linux 2018.03 v2.12.16 running Docker 18.06.1-ce + 64bit Amazon Linux 2016.09 v2.5.2 running Docker 1.12.6 + 64bit Amazon Linux 2018.03 v2.15.2 running Multi-container Docker 18.06.1-ce (Generic) + 64bit Debian jessie v2.12.16 running Go 1.4 (Preconfigured - Docker) + 64bit Debian jessie v2.12.16 running Go 1.3 (Preconfigured - Docker) + 64bit Debian jessie v2.12.16 running Python 3.4 (Preconfigured - Docker) + 64bit Debian jessie v2.10.0 running Python 3.4 (Preconfigured - Docker) + 64bit Amazon Linux 2018.03 v2.9.1 running Java 8 + 64bit Amazon Linux 2018.03 v2.9.1 running Java 7 + 64bit Amazon Linux 2018.03 v2.8.0 running Java 8 + 64bit Amazon Linux 2018.03 v2.7.6 running Java 8 + 64bit Amazon Linux 2018.03 v2.7.5 running Java 8 + 64bit Amazon Linux 2018.03 v2.7.4 running Java 8 + 64bit Amazon Linux 2018.03 v2.7.2 running Java 8 + 64bit Amazon Linux 2018.03 v2.7.1 running Java 8 + 64bit Amazon Linux 2017.09 v2.6.8 running Java 8 + 64bit Amazon Linux 2017.09 v2.6.5 running Java 8 + 64bit Amazon Linux 2017.09 v2.6.4 running Java 8 + 64bit Amazon Linux 2017.09 v2.6.3 running Java 8 + 64bit Amazon Linux 2017.09 v2.6.0 running Java 8 + 64bit Amazon Linux 2017.03 v2.5.4 running Java 8 + 64bit Amazon Linux 2017.03 v2.5.3 running Java 8 + 64bit Amazon Linux 2017.03 v2.5.2 running Java 8 + 64bit Amazon Linux 2016.09 v2.4.4 running Java 8 + 64bit Amazon Linux 2018.03 v2.12.1 running Go 1.12.7 + 64bit Amazon Linux 2018.03 v2.6.14 running Packer 1.0.3 + 64bit Amazon Linux 2018.03 v2.12.16 running GlassFish 5.0 Java 8 (Preconfigured - Docker) + + + + 64bit Amazon Linux 2018.03 v4.10.1 running Node.js + + zip + + + + 64bit Amazon Linux 2018.03 v4.9.2 running Node.js + + zip + + + + 64bit Amazon Linux 2018.03 v4.8.0 running Node.js + + zip + + + + 64bit Amazon Linux 2018.03 v4.6.0 running Node.js + + zip + + + + 64bit Amazon Linux 2018.03 v4.5.3 running Node.js + + zip + + + + 64bit Amazon Linux 2018.03 v4.5.1 running Node.js + + zip + + + + 64bit Amazon Linux 2018.03 v4.5.0 running Node.js + + zip + + + + 64bit Amazon Linux 2017.09 v4.4.6 running Node.js + + zip + + + + 64bit Amazon Linux 2017.09 v4.4.5 running Node.js + + zip + + + + 64bit Amazon Linux 2017.09 v4.4.4 running Node.js + + zip + + + + 64bit Amazon Linux 2017.09 v4.4.2 running Node.js + + zip + + + + 64bit Amazon Linux 2017.09 v4.4.0 running Node.js + + zip + + + + 64bit Amazon Linux 2017.03 v4.3.0 running Node.js + + zip + + + + 64bit Amazon Linux 2017.03 v4.2.2 running Node.js + + zip + + + + 64bit Amazon Linux 2017.03 v4.2.1 running Node.js + + zip + + + + 64bit Amazon Linux 2017.03 v4.2.0 running Node.js + + zip + + + + 64bit Amazon Linux 2017.03 v4.1.1 running Node.js + + zip + + + + 64bit Amazon Linux 2017.03 v4.1.0 running Node.js + + zip + + + + 64bit Amazon Linux 2016.09 v4.0.1 running Node.js + + zip + + + + 64bit Amazon Linux 2016.09 v4.0.0 running Node.js + + zip + + + + 64bit Amazon Linux 2016.09 v3.3.1 running Node.js + + zip + + + + 64bit Amazon Linux 2016.09 v3.1.0 running Node.js + + zip + + + + 64bit Amazon Linux 2018.03 v2.8.14 running PHP 5.4 + + zip + + + + 64bit Amazon Linux 2018.03 v2.8.14 running PHP 5.5 + + zip + + + + 64bit Amazon Linux 2018.03 v2.8.14 running PHP 5.6 + + zip + + + + 64bit Amazon Linux 2018.03 v2.8.14 running PHP 7.0 + + zip + + + + 64bit Amazon Linux 2018.03 v2.8.14 running PHP 7.1 + + zip + + + + 64bit Amazon Linux 2018.03 v2.8.14 running PHP 7.2 + + zip + + + + 64bit Amazon Linux 2018.03 v2.8.12 running PHP 7.2 + + zip + + + + 64bit Amazon Linux 2018.03 v2.8.7 running PHP 7.1 + + zip + + + + 64bit Amazon Linux 2018.03 v2.8.6 running PHP 7.1 + + zip + + + + 64bit Amazon Linux 2018.03 v2.8.6 running PHP 7.2 + + zip + + + + 64bit Amazon Linux 2018.03 v2.8.5 running PHP 7.2 + + zip + + + + 64bit Amazon Linux 2018.03 v2.8.4 running PHP 7.2 + + zip + + + + 64bit Amazon Linux 2018.03 v2.8.3 running PHP 7.2 + + zip + + + + 64bit Amazon Linux 2018.03 v2.8.2 running PHP 7.2 + + zip + + + + 64bit Amazon Linux 2018.03 v2.8.1 running PHP 7.2 + + zip + + + + 64bit Amazon Linux 2018.03 v2.8.0 running PHP 7.1 + + zip + + + + 64bit Amazon Linux 2018.03 v2.7.1 running PHP 5.6 + + zip + + + + 64bit Amazon Linux 2018.03 v2.7.1 running PHP 7.0 + + zip + + + + 64bit Amazon Linux 2018.03 v2.7.1 running PHP 7.1 + + zip + + + + 64bit Amazon Linux 2018.03 v2.7.0 running PHP 7.0 + + zip + + + + 64bit Amazon Linux 2018.03 v2.7.0 running PHP 7.1 + + zip + + + + 64bit Amazon Linux 2017.09 v2.6.6 running PHP 5.4 + + zip + + + + 64bit Amazon Linux 2017.09 v2.6.6 running PHP 5.6 + + zip + + + + 64bit Amazon Linux 2017.09 v2.6.6 running PHP 7.0 + + zip + + + + 64bit Amazon Linux 2017.09 v2.6.5 running PHP 7.0 + + zip + + + + 64bit Amazon Linux 2017.09 v2.6.4 running PHP 5.4 + + zip + + + + 64bit Amazon Linux 2017.09 v2.6.4 running PHP 5.5 + + zip + + + + 64bit Amazon Linux 2017.09 v2.6.4 running PHP 5.6 + + zip + + + + 64bit Amazon Linux 2017.09 v2.6.4 running PHP 7.0 + + zip + + + + 64bit Amazon Linux 2017.09 v2.6.4 running PHP 7.1 + + zip + + + + 64bit Amazon Linux 2017.09 v2.6.3 running PHP 5.4 + + zip + + + + 64bit Amazon Linux 2017.09 v2.6.3 running PHP 5.5 + + zip + + + + 64bit Amazon Linux 2017.09 v2.6.3 running PHP 5.6 + + zip + + + + 64bit Amazon Linux 2017.09 v2.6.3 running PHP 7.0 + + zip + + + + 64bit Amazon Linux 2017.09 v2.6.3 running PHP 7.1 + + zip + + + + 64bit Amazon Linux 2017.09 v2.6.2 running PHP 5.6 + + zip + + + + 64bit Amazon Linux 2017.09 v2.6.2 running PHP 7.0 + + zip + + + + 64bit Amazon Linux 2017.09 v2.6.1 running PHP 7.0 + + zip + + + + 64bit Amazon Linux 2017.09 v2.6.0 running PHP 5.4 + + zip + + + + 64bit Amazon Linux 2017.09 v2.6.0 running PHP 5.5 + + zip + + + + 64bit Amazon Linux 2017.09 v2.6.0 running PHP 5.6 + + zip + + + + 64bit Amazon Linux 2017.09 v2.6.0 running PHP 7.0 + + zip + + + + 64bit Amazon Linux 2017.09 v2.6.0 running PHP 7.1 + + zip + + + + 64bit Amazon Linux 2017.03 v2.5.0 running PHP 7.0 + + zip + + + + 64bit Amazon Linux 2017.03 v2.5.0 running PHP 7.1 + + zip + + + + 64bit Amazon Linux 2017.03 v2.4.4 running PHP 5.5 + + zip + + + + 64bit Amazon Linux 2017.03 v2.4.4 running PHP 5.6 + + zip + + + + 64bit Amazon Linux 2017.03 v2.4.4 running PHP 7.0 + + zip + + + + 64bit Amazon Linux 2017.03 v2.4.3 running PHP 7.0 + + zip + + + + 64bit Amazon Linux 2017.03 v2.4.2 running PHP 5.4 + + zip + + + + 64bit Amazon Linux 2017.03 v2.4.2 running PHP 5.5 + + zip + + + + 64bit Amazon Linux 2017.03 v2.4.2 running PHP 5.6 + + zip + + + + 64bit Amazon Linux 2017.03 v2.4.2 running PHP 7.0 + + zip + + + + 64bit Amazon Linux 2017.03 v2.4.1 running PHP 7.0 + + zip + + + + 64bit Amazon Linux 2017.03 v2.4.0 running PHP 7.0 + + zip + + + + 64bit Amazon Linux 2016.09 v2.3.2 running PHP 7.0 + + zip + + + + 64bit Amazon Linux 2016.09 v2.3.1 running PHP 7.0 + + zip + + + + 64bit Amazon Linux 2018.03 v2.9.1 running Python 3.6 + + zip + + + + 64bit Amazon Linux 2018.03 v2.9.1 running Python 3.4 + + zip + + + + 64bit Amazon Linux 2018.03 v2.9.1 running Python + + zip + + + + 64bit Amazon Linux 2018.03 v2.9.1 running Python 2.7 + + zip + + + + 64bit Amazon Linux 2018.03 v2.7.5 running Python 3.6 + + zip + + + + 64bit Amazon Linux 2018.03 v2.7.1 running Python 3.6 + + zip + + + + 64bit Amazon Linux 2018.03 v2.7.0 running Python 3.6 + + zip + + + + 64bit Amazon Linux 2017.09 v2.6.4 running Python 3.6 + + zip + + + + 64bit Amazon Linux 2017.09 v2.6.1 running Python 3.6 + + zip + + + + 64bit Amazon Linux 2017.03 v2.4.0 running Python 3.4 + + zip + + + + 64bit Amazon Linux 2018.03 v2.10.1 running Ruby 2.6 (Puma) + + zip + + + + 64bit Amazon Linux 2018.03 v2.10.1 running Ruby 2.5 (Puma) + + zip + + + + 64bit Amazon Linux 2018.03 v2.10.1 running Ruby 2.4 (Puma) + + zip + + + + 64bit Amazon Linux 2018.03 v2.10.1 running Ruby 2.3 (Puma) + + zip + + + + 64bit Amazon Linux 2018.03 v2.10.1 running Ruby 2.2 (Puma) + + zip + + + + 64bit Amazon Linux 2018.03 v2.10.1 running Ruby 2.1 (Puma) + + zip + + + + 64bit Amazon Linux 2018.03 v2.10.1 running Ruby 2.0 (Puma) + + zip + + + + 64bit Amazon Linux 2018.03 v2.10.1 running Ruby 2.6 (Passenger Standalone) + + zip + + + + 64bit Amazon Linux 2018.03 v2.10.1 running Ruby 2.5 (Passenger Standalone) + + zip + + + + 64bit Amazon Linux 2018.03 v2.10.1 running Ruby 2.4 (Passenger Standalone) + + zip + + + + 64bit Amazon Linux 2018.03 v2.10.1 running Ruby 2.3 (Passenger Standalone) + + zip + + + + 64bit Amazon Linux 2018.03 v2.10.1 running Ruby 2.2 (Passenger Standalone) + + zip + + + + 64bit Amazon Linux 2018.03 v2.10.1 running Ruby 2.1 (Passenger Standalone) + + zip + + + + 64bit Amazon Linux 2018.03 v2.10.1 running Ruby 2.0 (Passenger Standalone) + + zip + + + + 64bit Amazon Linux 2018.03 v2.10.1 running Ruby 1.9.3 + + zip + + + + 64bit Amazon Linux 2018.03 v2.8.0 running Ruby 2.5 (Passenger Standalone) + + zip + + + + 64bit Amazon Linux 2017.03 v2.4.4 running Ruby 2.3 (Puma) + + zip + + + + 64bit Amazon Linux 2017.03 v2.4.4 running Ruby 2.3 (Passenger Standalone) + + zip + + + + 64bit Amazon Linux 2018.03 v3.2.1 running Tomcat 8.5 Java 8 + + war + zip + + + + 64bit Amazon Linux 2018.03 v3.2.1 running Tomcat 8 Java 8 + + war + zip + + + + 64bit Amazon Linux 2018.03 v3.2.1 running Tomcat 7 Java 7 + + war + zip + + + + 64bit Amazon Linux 2018.03 v3.2.1 running Tomcat 7 Java 6 + + war + zip + + + + 64bit Amazon Linux 2018.03 v3.1.1 running Tomcat 8.5 Java 8 + + war + zip + + + + 64bit Amazon Linux 2017.03 v2.6.5 running Tomcat 8 Java 8 + + war + zip + + + + 64bit Amazon Linux 2017.03 v2.6.2 running Tomcat 8 Java 8 + + war + zip + + + + 64bit Amazon Linux 2017.03 v2.6.1 running Tomcat 8 Java 8 + + war + zip + + + + 64bit Amazon Linux 2017.03 v2.6.0 running Tomcat 8 Java 8 + + war + zip + + + + 64bit Amazon Linux 2016.09 v2.5.4 running Tomcat 8 Java 8 + + war + zip + + + + 64bit Amazon Linux 2016.03 v2.1.0 running Tomcat 8 Java 8 + + war + zip + + + + 64bit Windows Server Core 2016 v2.2.1 running IIS 10.0 + + zip + + + + 64bit Windows Server 2016 v2.2.1 running IIS 10.0 + + zip + + + + 64bit Windows Server Core 2012 R2 v2.2.1 running IIS 8.5 + + zip + + + + 64bit Windows Server 2012 R2 v2.2.1 running IIS 8.5 + + zip + + + + 64bit Windows Server Core 2016 v1.2.0 running IIS 10.0 + + zip + + + + 64bit Windows Server 2016 v1.2.0 running IIS 10.0 + + zip + + + + 64bit Windows Server Core 2012 R2 v1.2.0 running IIS 8.5 + + zip + + + + 64bit Windows Server 2012 R2 v1.2.0 running IIS 8.5 + + zip + + + + 64bit Windows Server 2012 v1.2.0 running IIS 8 + + zip + + + + 64bit Windows Server 2008 R2 v1.2.0 running IIS 7.5 + + zip + + + + 64bit Windows Server Core 2012 R2 running IIS 8.5 + + zip + + + + 64bit Windows Server 2012 R2 running IIS 8.5 + + zip + + + + 64bit Windows Server 2012 running IIS 8 + + zip + + + + 64bit Windows Server 2008 R2 running IIS 7.5 + + zip + + + + 64bit Amazon Linux 2018.03 v2.12.16 running Docker 18.06.1-ce + + + + 64bit Amazon Linux 2016.09 v2.5.2 running Docker 1.12.6 + + + + 64bit Amazon Linux 2018.03 v2.15.2 running Multi-container Docker 18.06.1-ce (Generic) + + zip + json + + + + 64bit Debian jessie v2.12.16 running Go 1.4 (Preconfigured - Docker) + + zip + + + + 64bit Debian jessie v2.12.16 running Go 1.3 (Preconfigured - Docker) + + zip + + + + 64bit Debian jessie v2.12.16 running Python 3.4 (Preconfigured - Docker) + + zip + + + + 64bit Debian jessie v2.10.0 running Python 3.4 (Preconfigured - Docker) + + zip + + + + 64bit Amazon Linux 2018.03 v2.9.1 running Java 8 + + jar + zip + + + + 64bit Amazon Linux 2018.03 v2.9.1 running Java 7 + + jar + zip + + + + 64bit Amazon Linux 2018.03 v2.8.0 running Java 8 + + jar + zip + + + + 64bit Amazon Linux 2018.03 v2.7.6 running Java 8 + + jar + zip + + + + 64bit Amazon Linux 2018.03 v2.7.5 running Java 8 + + jar + zip + + + + 64bit Amazon Linux 2018.03 v2.7.4 running Java 8 + + jar + zip + + + + 64bit Amazon Linux 2018.03 v2.7.2 running Java 8 + + jar + zip + + + + 64bit Amazon Linux 2018.03 v2.7.1 running Java 8 + + jar + zip + + + + 64bit Amazon Linux 2017.09 v2.6.8 running Java 8 + + jar + zip + + + + 64bit Amazon Linux 2017.09 v2.6.5 running Java 8 + + jar + zip + + + + 64bit Amazon Linux 2017.09 v2.6.4 running Java 8 + + jar + zip + + + + 64bit Amazon Linux 2017.09 v2.6.3 running Java 8 + + jar + zip + + + + 64bit Amazon Linux 2017.09 v2.6.0 running Java 8 + + jar + zip + + + + 64bit Amazon Linux 2017.03 v2.5.4 running Java 8 + + jar + zip + + + + 64bit Amazon Linux 2017.03 v2.5.3 running Java 8 + + jar + zip + + + + 64bit Amazon Linux 2017.03 v2.5.2 running Java 8 + + jar + zip + + + + 64bit Amazon Linux 2016.09 v2.4.4 running Java 8 + + jar + zip + + + + 64bit Amazon Linux 2018.03 v2.12.1 running Go 1.12.7 + + zip + + + + 64bit Amazon Linux 2018.03 v2.6.14 running Packer 1.0.3 + + + + 64bit Amazon Linux 2018.03 v2.12.16 running GlassFish 5.0 Java 8 (Preconfigured - Docker) + + zip + + + + + + bd6bd2b2-9983-4845-b53b-fe53e8a5e1e7 + + +""" diff --git a/tests/test_eb/test_eb.py b/tests/test_eb/test_eb.py index 9e863e7f..aafe524f 100644 --- a/tests/test_eb/test_eb.py +++ b/tests/test_eb/test_eb.py @@ -37,3 +37,44 @@ def test_describe_applications(): apps = conn.describe_applications() len(apps['Applications']).should.equal(1) apps['Applications'][0]['ApplicationName'].should.equal('myapp') + + +@mock_eb +def test_create_environment(): + # Create Elastic Beanstalk Environment + conn = boto3.client('elasticbeanstalk', region_name='us-east-1') + app = conn.create_application( + ApplicationName="myapp", + ) + env = conn.create_environment( + ApplicationName="myapp", + EnvironmentName="myenv", + ) + env['EnvironmentName'].should.equal("myenv") + + +@mock_eb +def test_describe_environments(): + # List Elastic Beanstalk Envs + conn = boto3.client('elasticbeanstalk', region_name='us-east-1') + conn.create_application( + ApplicationName="myapp", + ) + conn.create_environment( + ApplicationName="myapp", + EnvironmentName="myenv", + ) + + envs = conn.describe_environments() + envs = envs['Environments'] + len(envs).should.equal(1) + envs[0]['ApplicationName'].should.equal('myapp') + envs[0]['EnvironmentName'].should.equal('myenv') + + +@mock_eb +def test_list_available_solution_stacks(): + conn = boto3.client('elasticbeanstalk', region_name='us-east-1') + stacks = conn.list_available_solution_stacks() + len(stacks['SolutionStacks']).should.be.greater_than(0) + len(stacks['SolutionStacks']).should.be.equal(len(stacks['SolutionStackDetails'])) From 91fb40810242213349e0f436e01464425fdd0928 Mon Sep 17 00:00:00 2001 From: Niels Laukens Date: Wed, 4 Sep 2019 16:25:43 +0200 Subject: [PATCH 04/12] Move tags_from_query_string to core.utils --- moto/core/utils.py | 17 +++++++++++++++++ moto/ec2/responses/tags.py | 3 ++- moto/ec2/utils.py | 17 ----------------- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/moto/core/utils.py b/moto/core/utils.py index ca670e87..acf76bb4 100644 --- a/moto/core/utils.py +++ b/moto/core/utils.py @@ -297,3 +297,20 @@ def path_url(url): if parsed_url.query: path = path + '?' + parsed_url.query return path + + +def tags_from_query_string(querystring_dict): + prefix = 'Tag' + suffix = 'Key' + response_values = {} + for key, value in querystring_dict.items(): + if key.startswith(prefix) and key.endswith(suffix): + tag_index = key.replace(prefix + ".", "").replace("." + suffix, "") + tag_key = querystring_dict.get("Tag.{0}.Key".format(tag_index))[0] + tag_value_key = "Tag.{0}.Value".format(tag_index) + if tag_value_key in querystring_dict: + response_values[tag_key] = querystring_dict.get(tag_value_key)[ + 0] + else: + response_values[tag_key] = None + return response_values diff --git a/moto/ec2/responses/tags.py b/moto/ec2/responses/tags.py index 65d3da25..37f2c3be 100644 --- a/moto/ec2/responses/tags.py +++ b/moto/ec2/responses/tags.py @@ -2,7 +2,8 @@ from __future__ import unicode_literals from moto.core.responses import BaseResponse from moto.ec2.models import validate_resource_ids -from moto.ec2.utils import tags_from_query_string, filters_from_querystring +from moto.ec2.utils import filters_from_querystring +from moto.core.utils import tags_from_query_string class TagResponse(BaseResponse): diff --git a/moto/ec2/utils.py b/moto/ec2/utils.py index e67cb39f..f0d58d5f 100644 --- a/moto/ec2/utils.py +++ b/moto/ec2/utils.py @@ -198,23 +198,6 @@ def split_route_id(route_id): return values[0], values[1] -def tags_from_query_string(querystring_dict): - prefix = 'Tag' - suffix = 'Key' - response_values = {} - for key, value in querystring_dict.items(): - if key.startswith(prefix) and key.endswith(suffix): - tag_index = key.replace(prefix + ".", "").replace("." + suffix, "") - tag_key = querystring_dict.get("Tag.{0}.Key".format(tag_index))[0] - tag_value_key = "Tag.{0}.Value".format(tag_index) - if tag_value_key in querystring_dict: - response_values[tag_key] = querystring_dict.get(tag_value_key)[ - 0] - else: - response_values[tag_key] = None - return response_values - - def dhcp_configuration_from_querystring(querystring, option=u'DhcpConfiguration'): """ turn: From 9bfbd8e0088d93ccf7c0e4d81526f45db8f9bf50 Mon Sep 17 00:00:00 2001 From: Niels Laukens Date: Wed, 4 Sep 2019 16:55:34 +0200 Subject: [PATCH 05/12] Make tags_from_query_string() more flexible --- moto/core/utils.py | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/moto/core/utils.py b/moto/core/utils.py index acf76bb4..6f75619d 100644 --- a/moto/core/utils.py +++ b/moto/core/utils.py @@ -299,15 +299,27 @@ def path_url(url): return path -def tags_from_query_string(querystring_dict): - prefix = 'Tag' - suffix = 'Key' +def tags_from_query_string( + querystring_dict, + prefix="Tag", + key_suffix="Key", + value_suffix="Value" +): response_values = {} for key, value in querystring_dict.items(): - if key.startswith(prefix) and key.endswith(suffix): - tag_index = key.replace(prefix + ".", "").replace("." + suffix, "") - tag_key = querystring_dict.get("Tag.{0}.Key".format(tag_index))[0] - tag_value_key = "Tag.{0}.Value".format(tag_index) + if key.startswith(prefix) and key.endswith(key_suffix): + tag_index = key.replace(prefix + ".", "").replace("." + key_suffix, "") + tag_key = querystring_dict.get( + "{prefix}.{index}.{key_suffix}".format( + prefix=prefix, + index=tag_index, + key_suffix=key_suffix, + ))[0] + tag_value_key = "{prefix}.{index}.{value_suffix}".format( + prefix=prefix, + index=tag_index, + value_suffix=value_suffix, + ) if tag_value_key in querystring_dict: response_values[tag_key] = querystring_dict.get(tag_value_key)[ 0] From 7f387b0bb9842d3561f59ed7fa70b92e17791909 Mon Sep 17 00:00:00 2001 From: Niels Laukens Date: Wed, 4 Sep 2019 16:56:06 +0200 Subject: [PATCH 06/12] 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 c470d531..bf3a8961 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 5b465517..c3c2aa20 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 fecdb8c2..fbace193 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 aafe524f..2b5be449 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') From 8f51bd6116b7194d2ef553064a66ff4bb3af734a Mon Sep 17 00:00:00 2001 From: Niels Laukens Date: Thu, 5 Sep 2019 11:38:19 +0200 Subject: [PATCH 07/12] EB: pass through SolutionStackName --- moto/eb/models.py | 8 ++++---- moto/eb/responses.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/moto/eb/models.py b/moto/eb/models.py index c3c2aa20..fa7345f0 100644 --- a/moto/eb/models.py +++ b/moto/eb/models.py @@ -11,10 +11,12 @@ class FakeEnvironment(BaseModel): self, application, environment_name, + solution_stack_name, tags, ): self.application = weakref.proxy(application) # weakref to break circular dependencies self.environment_name = environment_name + self.solution_stack_name = solution_stack_name self.tags = tags @property @@ -35,10 +37,6 @@ class FakeEnvironment(BaseModel): def platform_arn(self): return 'TODO' # TODO - @property - def solution_stack_name(self): - return 'TODO' # TODO - @property def region(self): return self.application.region @@ -53,6 +51,7 @@ class FakeApplication(BaseModel): def create_environment( self, environment_name, + solution_stack_name, tags, ): if environment_name in self.environments: @@ -61,6 +60,7 @@ class FakeApplication(BaseModel): env = FakeEnvironment( application=self, environment_name=environment_name, + solution_stack_name=solution_stack_name, tags=tags, ) self.environments[environment_name] = env diff --git a/moto/eb/responses.py b/moto/eb/responses.py index fbace193..c93efc3a 100644 --- a/moto/eb/responses.py +++ b/moto/eb/responses.py @@ -31,7 +31,6 @@ class EBResponse(BaseResponse): def create_environment(self): application_name = self._get_param('ApplicationName') - environment_name = self._get_param('EnvironmentName') try: app = self.backend.applications[application_name] except KeyError: @@ -41,7 +40,8 @@ class EBResponse(BaseResponse): tags = tags_from_query_string(self.querystring, prefix="Tags.member") env = app.create_environment( - environment_name=environment_name, + environment_name=self._get_param('EnvironmentName'), + solution_stack_name=self._get_param('SolutionStackName'), tags=tags, ) From 7fae0d52ad6220998ef07dca7cf7de79680c2c80 Mon Sep 17 00:00:00 2001 From: Niels Laukens Date: Thu, 5 Sep 2019 14:17:55 +0200 Subject: [PATCH 08/12] Fix linting --- moto/eb/models.py | 12 ++++++------ moto/eb/responses.py | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/moto/eb/models.py b/moto/eb/models.py index fa7345f0..4490bbd0 100644 --- a/moto/eb/models.py +++ b/moto/eb/models.py @@ -26,12 +26,12 @@ class FakeEnvironment(BaseModel): @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, - ) + '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): diff --git a/moto/eb/responses.py b/moto/eb/responses.py index c93efc3a..905780c4 100644 --- a/moto/eb/responses.py +++ b/moto/eb/responses.py @@ -1,6 +1,6 @@ from moto.core.responses import BaseResponse from moto.core.utils import tags_from_query_string -from .models import eb_backends, EBBackend +from .models import eb_backends from .exceptions import InvalidParameterValueError, ResourceNotFoundException From 888e0c31a0fd94c8854f93ec88e3b45ebfeeb98b Mon Sep 17 00:00:00 2001 From: Bert Blommers Date: Mon, 30 Mar 2020 13:42:00 +0100 Subject: [PATCH 09/12] Linting --- moto/__init__.py | 15 +++--- moto/core/utils.py | 19 +++---- moto/eb/exceptions.py | 6 ++- moto/eb/models.py | 35 ++++++------ moto/eb/responses.py | 47 +++++++--------- moto/eb/urls.py | 2 +- tests/test_eb/test_eb.py | 112 ++++++++++++++++----------------------- 7 files changed, 96 insertions(+), 140 deletions(-) diff --git a/moto/__init__.py b/moto/__init__.py index c2caa8df..9b59f18e 100644 --- a/moto/__init__.py +++ b/moto/__init__.py @@ -1,11 +1,4 @@ from __future__ import unicode_literals -import logging -# logging.getLogger('boto').setLevel(logging.CRITICAL) - - -__title__ = "moto" -__version__ = "1.3.15.dev" - from .acm import mock_acm # noqa from .apigateway import mock_apigateway, mock_apigateway_deprecated # noqa @@ -28,7 +21,7 @@ from .datasync import mock_datasync # noqa from .dynamodb import mock_dynamodb, mock_dynamodb_deprecated # noqa from .dynamodb2 import mock_dynamodb2, mock_dynamodb2_deprecated # noqa from .dynamodbstreams import mock_dynamodbstreams # noqa -from .eb import mock_eb # flake8: noqa +from .eb import mock_eb # noqa from .ec2 import mock_ec2, mock_ec2_deprecated # noqa from .ec2_instance_connect import mock_ec2_instance_connect # noqa from .ecr import mock_ecr, mock_ecr_deprecated # noqa @@ -65,6 +58,12 @@ from .sts import mock_sts, mock_sts_deprecated # noqa from .swf import mock_swf, mock_swf_deprecated # noqa from .xray import XRaySegment, mock_xray, mock_xray_client # noqa +# import logging +# logging.getLogger('boto').setLevel(logging.CRITICAL) + +__title__ = "moto" +__version__ = "1.3.15.dev" + try: # Need to monkey-patch botocore requests back to underlying urllib3 classes diff --git a/moto/core/utils.py b/moto/core/utils.py index 59079784..dce9f675 100644 --- a/moto/core/utils.py +++ b/moto/core/utils.py @@ -331,10 +331,7 @@ def py2_strip_unicode_keys(blob): def tags_from_query_string( - querystring_dict, - prefix="Tag", - key_suffix="Key", - value_suffix="Value" + querystring_dict, prefix="Tag", key_suffix="Key", value_suffix="Value" ): response_values = {} for key, value in querystring_dict.items(): @@ -342,18 +339,14 @@ def tags_from_query_string( tag_index = key.replace(prefix + ".", "").replace("." + key_suffix, "") tag_key = querystring_dict.get( "{prefix}.{index}.{key_suffix}".format( - prefix=prefix, - index=tag_index, - key_suffix=key_suffix, - ))[0] + prefix=prefix, index=tag_index, key_suffix=key_suffix, + ) + )[0] tag_value_key = "{prefix}.{index}.{value_suffix}".format( - prefix=prefix, - index=tag_index, - value_suffix=value_suffix, + prefix=prefix, index=tag_index, value_suffix=value_suffix, ) if tag_value_key in querystring_dict: - response_values[tag_key] = querystring_dict.get(tag_value_key)[ - 0] + response_values[tag_key] = querystring_dict.get(tag_value_key)[0] else: response_values[tag_key] = None return response_values diff --git a/moto/eb/exceptions.py b/moto/eb/exceptions.py index bf3a8961..f1e27c56 100644 --- a/moto/eb/exceptions.py +++ b/moto/eb/exceptions.py @@ -4,10 +4,12 @@ from moto.core.exceptions import RESTError class InvalidParameterValueError(RESTError): def __init__(self, message): super(InvalidParameterValueError, self).__init__( - "InvalidParameterValue", message) + "InvalidParameterValue", message + ) class ResourceNotFoundException(RESTError): def __init__(self, message): super(ResourceNotFoundException, self).__init__( - "ResourceNotFoundException", message) + "ResourceNotFoundException", message + ) diff --git a/moto/eb/models.py b/moto/eb/models.py index 4490bbd0..71873f30 100644 --- a/moto/eb/models.py +++ b/moto/eb/models.py @@ -8,13 +8,11 @@ from .exceptions import InvalidParameterValueError class FakeEnvironment(BaseModel): def __init__( - self, - application, - environment_name, - solution_stack_name, - tags, + self, application, environment_name, solution_stack_name, tags, ): - self.application = weakref.proxy(application) # weakref to break circular dependencies + self.application = weakref.proxy( + application + ) # weakref to break circular dependencies self.environment_name = environment_name self.solution_stack_name = solution_stack_name self.tags = tags @@ -25,17 +23,19 @@ class FakeEnvironment(BaseModel): @property def environment_arn(self): - return 'arn:aws:elasticbeanstalk:{region}:{account_id}:' \ - 'environment/{application_name}/{environment_name}'.format( + return ( + "arn:aws:elasticbeanstalk:{region}:{account_id}:" + "environment/{application_name}/{environment_name}".format( region=self.region, - account_id='123456789012', + account_id="123456789012", application_name=self.application_name, environment_name=self.environment_name, ) + ) @property def platform_arn(self): - return 'TODO' # TODO + return "TODO" # TODO @property def region(self): @@ -49,10 +49,7 @@ class FakeApplication(BaseModel): self.environments = dict() def create_environment( - self, - environment_name, - solution_stack_name, - tags, + self, environment_name, solution_stack_name, tags, ): if environment_name in self.environments: raise InvalidParameterValueError @@ -89,13 +86,11 @@ class EBBackend(BaseBackend): raise InvalidParameterValueError( "Application {} already exists.".format(application_name) ) - new_app = FakeApplication( - backend=self, - application_name=application_name, - ) + new_app = FakeApplication(backend=self, application_name=application_name,) self.applications[application_name] = new_app return new_app -eb_backends = dict((region.name, EBBackend(region.name)) - for region in boto.beanstalk.regions()) +eb_backends = dict( + (region.name, EBBackend(region.name)) for region in boto.beanstalk.regions() +) diff --git a/moto/eb/responses.py b/moto/eb/responses.py index 905780c4..6178e4a7 100644 --- a/moto/eb/responses.py +++ b/moto/eb/responses.py @@ -14,42 +14,34 @@ class EBResponse(BaseResponse): def create_application(self): app = self.backend.create_application( - application_name=self._get_param('ApplicationName'), + application_name=self._get_param("ApplicationName"), ) template = self.response_template(EB_CREATE_APPLICATION) - return template.render( - region_name=self.backend.region, - application=app, - ) + return template.render(region_name=self.backend.region, application=app,) def describe_applications(self): template = self.response_template(EB_DESCRIBE_APPLICATIONS) - return template.render( - applications=self.backend.applications.values(), - ) + return template.render(applications=self.backend.applications.values(),) def create_environment(self): - application_name = self._get_param('ApplicationName') + application_name = self._get_param("ApplicationName") try: app = self.backend.applications[application_name] except KeyError: raise InvalidParameterValueError( - "No Application named \'{}\' found.".format(application_name) + "No Application named '{}' found.".format(application_name) ) tags = tags_from_query_string(self.querystring, prefix="Tags.member") env = app.create_environment( - environment_name=self._get_param('EnvironmentName'), - solution_stack_name=self._get_param('SolutionStackName'), + environment_name=self._get_param("EnvironmentName"), + solution_stack_name=self._get_param("SolutionStackName"), tags=tags, ) template = self.response_template(EB_CREATE_ENVIRONMENT) - return template.render( - environment=env, - region=self.backend.region, - ) + return template.render(environment=env, region=self.backend.region,) def describe_environments(self): envs = [] @@ -59,9 +51,7 @@ class EBResponse(BaseResponse): envs.append(env) template = self.response_template(EB_DESCRIBE_ENVIRONMENTS) - return template.render( - environments=envs, - ) + return template.render(environments=envs,) @staticmethod def list_available_solution_stacks(): @@ -75,39 +65,38 @@ class EBResponse(BaseResponse): raise KeyError() def update_tags_for_resource(self): - resource_arn = self._get_param('ResourceArn') + 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) + "Resource not found for ARN '{}'.".format(resource_arn) ) - tags_to_add = tags_from_query_string(self.querystring, prefix="TagsToAdd.member") + 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') + 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') + 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) + "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, - ) + return template.render(tags=tags, arn=resource_arn,) EB_CREATE_APPLICATION = """ diff --git a/moto/eb/urls.py b/moto/eb/urls.py index 4cd4add1..2d57f7f9 100644 --- a/moto/eb/urls.py +++ b/moto/eb/urls.py @@ -7,5 +7,5 @@ url_bases = [ ] url_paths = { - '{0}/$': EBResponse.dispatch, + "{0}/$": EBResponse.dispatch, } diff --git a/tests/test_eb/test_eb.py b/tests/test_eb/test_eb.py index 2b5be449..1064bf31 100644 --- a/tests/test_eb/test_eb.py +++ b/tests/test_eb/test_eb.py @@ -8,114 +8,94 @@ from moto import mock_eb @mock_eb def test_create_application(): # Create Elastic Beanstalk Application - conn = boto3.client('elasticbeanstalk', region_name='us-east-1') - app = conn.create_application( - ApplicationName="myapp", - ) - app['Application']['ApplicationName'].should.equal("myapp") + conn = boto3.client("elasticbeanstalk", region_name="us-east-1") + app = conn.create_application(ApplicationName="myapp",) + app["Application"]["ApplicationName"].should.equal("myapp") @mock_eb def test_create_application_dup(): - conn = boto3.client('elasticbeanstalk', region_name='us-east-1') - conn.create_application( - ApplicationName="myapp", + conn = boto3.client("elasticbeanstalk", region_name="us-east-1") + conn.create_application(ApplicationName="myapp",) + conn.create_application.when.called_with(ApplicationName="myapp",).should.throw( + ClientError ) - conn.create_application.when.called_with( - ApplicationName="myapp", - ).should.throw(ClientError) @mock_eb def test_describe_applications(): # Create Elastic Beanstalk Application - conn = boto3.client('elasticbeanstalk', region_name='us-east-1') - conn.create_application( - ApplicationName="myapp", - ) + conn = boto3.client("elasticbeanstalk", region_name="us-east-1") + conn.create_application(ApplicationName="myapp",) apps = conn.describe_applications() - len(apps['Applications']).should.equal(1) - apps['Applications'][0]['ApplicationName'].should.equal('myapp') + len(apps["Applications"]).should.equal(1) + apps["Applications"][0]["ApplicationName"].should.equal("myapp") @mock_eb def test_create_environment(): # Create Elastic Beanstalk Environment - conn = boto3.client('elasticbeanstalk', region_name='us-east-1') - app = conn.create_application( - ApplicationName="myapp", - ) - env = conn.create_environment( - ApplicationName="myapp", - EnvironmentName="myenv", - ) - env['EnvironmentName'].should.equal("myenv") + conn = boto3.client("elasticbeanstalk", region_name="us-east-1") + app = conn.create_application(ApplicationName="myapp",) + env = conn.create_environment(ApplicationName="myapp", EnvironmentName="myenv",) + env["EnvironmentName"].should.equal("myenv") @mock_eb def test_describe_environments(): # List Elastic Beanstalk Envs - conn = boto3.client('elasticbeanstalk', region_name='us-east-1') - conn.create_application( - ApplicationName="myapp", - ) + conn = boto3.client("elasticbeanstalk", region_name="us-east-1") + conn.create_application(ApplicationName="myapp",) conn.create_environment( - ApplicationName="myapp", - EnvironmentName="myenv", + ApplicationName="myapp", EnvironmentName="myenv", ) envs = conn.describe_environments() - envs = envs['Environments'] + envs = envs["Environments"] len(envs).should.equal(1) - envs[0]['ApplicationName'].should.equal('myapp') - envs[0]['EnvironmentName'].should.equal('myenv') + envs[0]["ApplicationName"].should.equal("myapp") + 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}) + 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'] + 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'} + 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) + 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", - ) + 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', + "initial key": "initial value", + "to remove": "delete me", + "to update": "original", } env = conn.create_environment( ApplicationName="myapp", @@ -124,29 +104,27 @@ def test_update_tags(): ) extra_env_tags = { - 'to update': 'new', - 'extra key': 'extra value', + "to update": "new", + "extra key": "extra value", } conn.update_tags_for_resource( - ResourceArn=env['EnvironmentArn'], + ResourceArn=env["EnvironmentArn"], TagsToAdd=tags_dict_to_list(extra_env_tags), - TagsToRemove=['to remove'], + TagsToRemove=["to remove"], ) total_env_tags = env_tags.copy() total_env_tags.update(extra_env_tags) - del total_env_tags['to remove'] + 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) + 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') + conn = boto3.client("elasticbeanstalk", region_name="us-east-1") stacks = conn.list_available_solution_stacks() - len(stacks['SolutionStacks']).should.be.greater_than(0) - len(stacks['SolutionStacks']).should.be.equal(len(stacks['SolutionStackDetails'])) + len(stacks["SolutionStacks"]).should.be.greater_than(0) + len(stacks["SolutionStacks"]).should.be.equal(len(stacks["SolutionStackDetails"])) From c32c17a13ed94b3867336e2a9e16c5890ad75973 Mon Sep 17 00:00:00 2001 From: Bert Blommers Date: Mon, 30 Mar 2020 13:49:19 +0100 Subject: [PATCH 10/12] Remove duplicated method --- moto/ec2/utils.py | 16 ---------------- moto/emr/responses.py | 5 +++-- moto/emr/utils.py | 16 ---------------- 3 files changed, 3 insertions(+), 34 deletions(-) diff --git a/moto/ec2/utils.py b/moto/ec2/utils.py index 74fe3d27..61d22d8b 100644 --- a/moto/ec2/utils.py +++ b/moto/ec2/utils.py @@ -196,22 +196,6 @@ def split_route_id(route_id): return values[0], values[1] -def tags_from_query_string(querystring_dict): - prefix = "Tag" - suffix = "Key" - response_values = {} - for key, value in querystring_dict.items(): - if key.startswith(prefix) and key.endswith(suffix): - tag_index = key.replace(prefix + ".", "").replace("." + suffix, "") - tag_key = querystring_dict.get("Tag.{0}.Key".format(tag_index))[0] - tag_value_key = "Tag.{0}.Value".format(tag_index) - if tag_value_key in querystring_dict: - response_values[tag_key] = querystring_dict.get(tag_value_key)[0] - else: - response_values[tag_key] = None - return response_values - - def dhcp_configuration_from_querystring(querystring, option="DhcpConfiguration"): """ turn: diff --git a/moto/emr/responses.py b/moto/emr/responses.py index 3708db0e..d2b234ce 100644 --- a/moto/emr/responses.py +++ b/moto/emr/responses.py @@ -10,9 +10,10 @@ from six.moves.urllib.parse import urlparse from moto.core.responses import AWSServiceSpec from moto.core.responses import BaseResponse from moto.core.responses import xml_to_json_response +from moto.core.utils import tags_from_query_string from .exceptions import EmrError from .models import emr_backends -from .utils import steps_from_query_string, tags_from_query_string +from .utils import steps_from_query_string def generate_boto3_response(operation): @@ -91,7 +92,7 @@ class ElasticMapReduceResponse(BaseResponse): @generate_boto3_response("AddTags") def add_tags(self): cluster_id = self._get_param("ResourceId") - tags = tags_from_query_string(self.querystring) + tags = tags_from_query_string(self.querystring, prefix="Tags") self.backend.add_tags(cluster_id, tags) template = self.response_template(ADD_TAGS_TEMPLATE) return template.render() diff --git a/moto/emr/utils.py b/moto/emr/utils.py index 0f75995b..fb33214c 100644 --- a/moto/emr/utils.py +++ b/moto/emr/utils.py @@ -22,22 +22,6 @@ def random_instance_group_id(size=13): return "i-{0}".format(random_id()) -def tags_from_query_string(querystring_dict): - prefix = "Tags" - suffix = "Key" - response_values = {} - for key, value in querystring_dict.items(): - if key.startswith(prefix) and key.endswith(suffix): - tag_index = key.replace(prefix + ".", "").replace("." + suffix, "") - tag_key = querystring_dict.get("Tags.{0}.Key".format(tag_index))[0] - tag_value_key = "Tags.{0}.Value".format(tag_index) - if tag_value_key in querystring_dict: - response_values[tag_key] = querystring_dict.get(tag_value_key)[0] - else: - response_values[tag_key] = None - return response_values - - def steps_from_query_string(querystring_dict): steps = [] for step in querystring_dict: From 7d524eaec9bb49f8d3e8e55a7f84c1876cd4e3d1 Mon Sep 17 00:00:00 2001 From: Bert Blommers Date: Mon, 30 Mar 2020 14:08:22 +0100 Subject: [PATCH 11/12] Elastic Beanstalk - Rename and Add Implementation Coverage --- IMPLEMENTATION_COVERAGE.md | 14 +++--- moto/__init__.py | 2 +- moto/{eb => elasticbeanstalk}/__init__.py | 2 +- moto/{eb => elasticbeanstalk}/exceptions.py | 0 moto/{eb => elasticbeanstalk}/models.py | 50 ++++++++++++++++++++- moto/{eb => elasticbeanstalk}/responses.py | 41 +++-------------- moto/{eb => elasticbeanstalk}/urls.py | 0 tests/test_eb/test_eb.py | 18 ++++---- 8 files changed, 74 insertions(+), 53 deletions(-) rename moto/{eb => elasticbeanstalk}/__init__.py (59%) rename moto/{eb => elasticbeanstalk}/exceptions.py (100%) rename moto/{eb => elasticbeanstalk}/models.py (61%) rename moto/{eb => elasticbeanstalk}/responses.py (97%) rename moto/{eb => elasticbeanstalk}/urls.py (100%) diff --git a/IMPLEMENTATION_COVERAGE.md b/IMPLEMENTATION_COVERAGE.md index 70561852..bd9e9a4c 100644 --- a/IMPLEMENTATION_COVERAGE.md +++ b/IMPLEMENTATION_COVERAGE.md @@ -2878,15 +2878,15 @@ - [ ] test_failover ## elasticbeanstalk -0% implemented +13% implemented - [ ] abort_environment_update - [ ] apply_environment_managed_action - [ ] check_dns_availability - [ ] compose_environments -- [ ] create_application +- [X] create_application - [ ] create_application_version - [ ] create_configuration_template -- [ ] create_environment +- [X] create_environment - [ ] create_platform_version - [ ] create_storage_location - [ ] delete_application @@ -2903,13 +2903,13 @@ - [ ] describe_environment_managed_action_history - [ ] describe_environment_managed_actions - [ ] describe_environment_resources -- [ ] describe_environments +- [X] describe_environments - [ ] describe_events - [ ] describe_instances_health - [ ] describe_platform_version -- [ ] list_available_solution_stacks +- [X] list_available_solution_stacks - [ ] list_platform_versions -- [ ] list_tags_for_resource +- [X] list_tags_for_resource - [ ] rebuild_environment - [ ] request_environment_info - [ ] restart_app_server @@ -2921,7 +2921,7 @@ - [ ] update_application_version - [ ] update_configuration_template - [ ] update_environment -- [ ] update_tags_for_resource +- [X] update_tags_for_resource - [ ] validate_configuration_settings ## elastictranscoder diff --git a/moto/__init__.py b/moto/__init__.py index 9b59f18e..4c9d4753 100644 --- a/moto/__init__.py +++ b/moto/__init__.py @@ -21,7 +21,7 @@ from .datasync import mock_datasync # noqa from .dynamodb import mock_dynamodb, mock_dynamodb_deprecated # noqa from .dynamodb2 import mock_dynamodb2, mock_dynamodb2_deprecated # noqa from .dynamodbstreams import mock_dynamodbstreams # noqa -from .eb import mock_eb # noqa +from .elasticbeanstalk import mock_elasticbeanstalk # noqa from .ec2 import mock_ec2, mock_ec2_deprecated # noqa from .ec2_instance_connect import mock_ec2_instance_connect # noqa from .ecr import mock_ecr, mock_ecr_deprecated # noqa diff --git a/moto/eb/__init__.py b/moto/elasticbeanstalk/__init__.py similarity index 59% rename from moto/eb/__init__.py rename to moto/elasticbeanstalk/__init__.py index 3e06e959..851fa445 100644 --- a/moto/eb/__init__.py +++ b/moto/elasticbeanstalk/__init__.py @@ -1,4 +1,4 @@ from .models import eb_backends from moto.core.models import base_decorator -mock_eb = base_decorator(eb_backends) +mock_elasticbeanstalk = base_decorator(eb_backends) diff --git a/moto/eb/exceptions.py b/moto/elasticbeanstalk/exceptions.py similarity index 100% rename from moto/eb/exceptions.py rename to moto/elasticbeanstalk/exceptions.py diff --git a/moto/eb/models.py b/moto/elasticbeanstalk/models.py similarity index 61% rename from moto/eb/models.py rename to moto/elasticbeanstalk/models.py index 71873f30..83ad65ab 100644 --- a/moto/eb/models.py +++ b/moto/elasticbeanstalk/models.py @@ -3,7 +3,7 @@ import weakref import boto.beanstalk from moto.core import BaseBackend, BaseModel -from .exceptions import InvalidParameterValueError +from .exceptions import InvalidParameterValueError, ResourceNotFoundException class FakeEnvironment(BaseModel): @@ -90,6 +90,54 @@ class EBBackend(BaseBackend): self.applications[application_name] = new_app return new_app + def create_environment(self, app, environment_name, stack_name, tags): + return app.create_environment( + environment_name=environment_name, + solution_stack_name=stack_name, + tags=tags, + ) + + def describe_environments(self): + envs = [] + for app in self.applications.values(): + for env in app.environments.values(): + envs.append(env) + return envs + + def list_available_solution_stacks(self): + # Implemented in response.py + pass + + def update_tags_for_resource(self, resource_arn, tags_to_add, tags_to_remove): + try: + res = self._find_environment_by_arn(resource_arn) + except KeyError: + raise ResourceNotFoundException( + "Resource not found for ARN '{}'.".format(resource_arn) + ) + + for key, value in tags_to_add.items(): + res.tags[key] = value + + for key in tags_to_remove: + del res.tags[key] + + def list_tags_for_resource(self, resource_arn): + try: + res = self._find_environment_by_arn(resource_arn) + except KeyError: + raise ResourceNotFoundException( + "Resource not found for ARN '{}'.".format(resource_arn) + ) + return res.tags + + def _find_environment_by_arn(self, arn): + for app in self.applications.keys(): + for env in self.applications[app].environments.values(): + if env.environment_arn == arn: + return env + raise KeyError() + eb_backends = dict( (region.name, EBBackend(region.name)) for region in boto.beanstalk.regions() diff --git a/moto/eb/responses.py b/moto/elasticbeanstalk/responses.py similarity index 97% rename from moto/eb/responses.py rename to moto/elasticbeanstalk/responses.py index 6178e4a7..0416121b 100644 --- a/moto/eb/responses.py +++ b/moto/elasticbeanstalk/responses.py @@ -1,7 +1,7 @@ from moto.core.responses import BaseResponse from moto.core.utils import tags_from_query_string from .models import eb_backends -from .exceptions import InvalidParameterValueError, ResourceNotFoundException +from .exceptions import InvalidParameterValueError class EBResponse(BaseResponse): @@ -34,9 +34,10 @@ class EBResponse(BaseResponse): ) tags = tags_from_query_string(self.querystring, prefix="Tags.member") - env = app.create_environment( + env = self.backend.create_environment( + app, environment_name=self._get_param("EnvironmentName"), - solution_stack_name=self._get_param("SolutionStackName"), + stack_name=self._get_param("SolutionStackName"), tags=tags, ) @@ -44,11 +45,7 @@ class EBResponse(BaseResponse): return template.render(environment=env, region=self.backend.region,) def describe_environments(self): - envs = [] - - for app in self.backend.applications.values(): - for env in app.environments.values(): - envs.append(env) + envs = self.backend.describe_environments() template = self.response_template(EB_DESCRIBE_ENVIRONMENTS) return template.render(environments=envs,) @@ -57,43 +54,19 @@ 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] + self.backend.update_tags_for_resource(resource_arn, tags_to_add, tags_to_remove) 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 + tags = self.backend.list_tags_for_resource(resource_arn) template = self.response_template(EB_LIST_TAGS_FOR_RESOURCE) return template.render(tags=tags, arn=resource_arn,) diff --git a/moto/eb/urls.py b/moto/elasticbeanstalk/urls.py similarity index 100% rename from moto/eb/urls.py rename to moto/elasticbeanstalk/urls.py diff --git a/tests/test_eb/test_eb.py b/tests/test_eb/test_eb.py index 1064bf31..42eb09be 100644 --- a/tests/test_eb/test_eb.py +++ b/tests/test_eb/test_eb.py @@ -2,10 +2,10 @@ import boto3 import sure # noqa from botocore.exceptions import ClientError -from moto import mock_eb +from moto import mock_elasticbeanstalk -@mock_eb +@mock_elasticbeanstalk def test_create_application(): # Create Elastic Beanstalk Application conn = boto3.client("elasticbeanstalk", region_name="us-east-1") @@ -13,7 +13,7 @@ def test_create_application(): app["Application"]["ApplicationName"].should.equal("myapp") -@mock_eb +@mock_elasticbeanstalk def test_create_application_dup(): conn = boto3.client("elasticbeanstalk", region_name="us-east-1") conn.create_application(ApplicationName="myapp",) @@ -22,7 +22,7 @@ def test_create_application_dup(): ) -@mock_eb +@mock_elasticbeanstalk def test_describe_applications(): # Create Elastic Beanstalk Application conn = boto3.client("elasticbeanstalk", region_name="us-east-1") @@ -33,7 +33,7 @@ def test_describe_applications(): apps["Applications"][0]["ApplicationName"].should.equal("myapp") -@mock_eb +@mock_elasticbeanstalk def test_create_environment(): # Create Elastic Beanstalk Environment conn = boto3.client("elasticbeanstalk", region_name="us-east-1") @@ -42,7 +42,7 @@ def test_create_environment(): env["EnvironmentName"].should.equal("myenv") -@mock_eb +@mock_elasticbeanstalk def test_describe_environments(): # List Elastic Beanstalk Envs conn = boto3.client("elasticbeanstalk", region_name="us-east-1") @@ -72,7 +72,7 @@ def tags_list_to_dict(tag_list): return tag_dict -@mock_eb +@mock_elasticbeanstalk def test_create_environment_tags(): conn = boto3.client("elasticbeanstalk", region_name="us-east-1") conn.create_application(ApplicationName="myapp",) @@ -88,7 +88,7 @@ def test_create_environment_tags(): tags_list_to_dict(tags["ResourceTags"]).should.equal(env_tags) -@mock_eb +@mock_elasticbeanstalk def test_update_tags(): conn = boto3.client("elasticbeanstalk", region_name="us-east-1") conn.create_application(ApplicationName="myapp",) @@ -122,7 +122,7 @@ def test_update_tags(): tags_list_to_dict(tags["ResourceTags"]).should.equal(total_env_tags) -@mock_eb +@mock_elasticbeanstalk def test_list_available_solution_stacks(): conn = boto3.client("elasticbeanstalk", region_name="us-east-1") stacks = conn.list_available_solution_stacks() From 551dc024595cc602091ecf640311a3db4a52d6ca Mon Sep 17 00:00:00 2001 From: Bert Blommers Date: Mon, 30 Mar 2020 16:28:36 +0100 Subject: [PATCH 12/12] ElasticBeanstalk - Fix tests in Python2 and ServerMode --- moto/backends.py | 2 ++ moto/elasticbeanstalk/models.py | 16 ++++++++++++---- moto/elasticbeanstalk/responses.py | 3 +-- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/moto/backends.py b/moto/backends.py index a358b8fd..a48df74a 100644 --- a/moto/backends.py +++ b/moto/backends.py @@ -23,6 +23,7 @@ from moto.ec2 import ec2_backends from moto.ec2_instance_connect import ec2_instance_connect_backends from moto.ecr import ecr_backends from moto.ecs import ecs_backends +from moto.elasticbeanstalk import eb_backends from moto.elb import elb_backends from moto.elbv2 import elbv2_backends from moto.emr import emr_backends @@ -77,6 +78,7 @@ BACKENDS = { "ec2_instance_connect": ec2_instance_connect_backends, "ecr": ecr_backends, "ecs": ecs_backends, + "elasticbeanstalk": eb_backends, "elb": elb_backends, "elbv2": elbv2_backends, "events": events_backends, diff --git a/moto/elasticbeanstalk/models.py b/moto/elasticbeanstalk/models.py index 83ad65ab..3767846c 100644 --- a/moto/elasticbeanstalk/models.py +++ b/moto/elasticbeanstalk/models.py @@ -1,6 +1,6 @@ import weakref -import boto.beanstalk +from boto3 import Session from moto.core import BaseBackend, BaseModel from .exceptions import InvalidParameterValueError, ResourceNotFoundException @@ -139,6 +139,14 @@ class EBBackend(BaseBackend): raise KeyError() -eb_backends = dict( - (region.name, EBBackend(region.name)) for region in boto.beanstalk.regions() -) +eb_backends = {} +for region in Session().get_available_regions("elasticbeanstalk"): + eb_backends[region] = EBBackend(region) +for region in Session().get_available_regions( + "elasticbeanstalk", partition_name="aws-us-gov" +): + eb_backends[region] = EBBackend(region) +for region in Session().get_available_regions( + "elasticbeanstalk", partition_name="aws-cn" +): + eb_backends[region] = EBBackend(region) diff --git a/moto/elasticbeanstalk/responses.py b/moto/elasticbeanstalk/responses.py index 0416121b..387cbb3e 100644 --- a/moto/elasticbeanstalk/responses.py +++ b/moto/elasticbeanstalk/responses.py @@ -50,8 +50,7 @@ class EBResponse(BaseResponse): template = self.response_template(EB_DESCRIBE_ENVIRONMENTS) return template.render(environments=envs,) - @staticmethod - def list_available_solution_stacks(): + def list_available_solution_stacks(self): return EB_LIST_AVAILABLE_SOLUTION_STACKS def update_tags_for_resource(self):