From c3e4b5401c526d9b826ab4404da4f718792bc80c Mon Sep 17 00:00:00 2001 From: Jon Haddad Date: Wed, 30 Oct 2013 17:55:13 -0700 Subject: [PATCH 01/35] added new vpc security group test. failing --- tests/test_ec2/test_security_groups.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/test_ec2/test_security_groups.py b/tests/test_ec2/test_security_groups.py index ce8de872b..606faebc3 100644 --- a/tests/test_ec2/test_security_groups.py +++ b/tests/test_ec2/test_security_groups.py @@ -20,6 +20,27 @@ def test_create_and_describe_security_group(): all_groups.should.have.length_of(1) all_groups[0].name.should.equal('test security group') +@mock_ec2 +def test_create_and_describe_vpc_security_group(): + conn = boto.connect_ec2('the_key', 'the_secret') + vpc_id = 'vpc-5300000c' + security_group = conn.create_security_group('test security group', 'this is a test security group', vpc_id) + + security_group.vpc_id.should.equal(vpc_id) + + security_group.name.should.equal('test security group') + security_group.description.should.equal('this is a test security group') + + # Trying to create another group with the same name should throw an error + conn.create_security_group.when.called_with('test security group', 'this is a test security group').should.throw(EC2ResponseError) + + all_groups = conn.get_all_security_groups() + + all_groups[0].vpc_id.should.equal(vpc_id) + + all_groups.should.have.length_of(1) + all_groups[0].name.should.equal('test security group') + @mock_ec2 def test_deleting_security_groups(): From 9ae144bad8e1182e7ef88774ebd6b72658c0a74a Mon Sep 17 00:00:00 2001 From: Jon Haddad Date: Wed, 30 Oct 2013 18:50:42 -0700 Subject: [PATCH 02/35] correctly getting the vpc address back --- moto/ec2/models.py | 9 ++++++--- moto/ec2/responses/security_groups.py | 5 +++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/moto/ec2/models.py b/moto/ec2/models.py index 0a391c5d7..95903b82f 100644 --- a/moto/ec2/models.py +++ b/moto/ec2/models.py @@ -300,27 +300,30 @@ class SecurityRule(object): class SecurityGroup(object): - def __init__(self, group_id, name, description): + def __init__(self, group_id, name, description, vpc_id=None): self.id = group_id self.name = name self.description = description self.ingress_rules = [] self.egress_rules = [] + self.vpc_id = vpc_id class SecurityGroupBackend(object): def __init__(self): self.groups = {} + self.vpc_groups = {} + super(SecurityGroupBackend, self).__init__() - def create_security_group(self, name, description, force=False): + def create_security_group(self, name, description, vpc_id=None, force=False): group_id = random_security_group_id() if not force: existing_group = self.get_security_group_from_name(name) if existing_group: return None - group = SecurityGroup(group_id, name, description) + group = SecurityGroup(group_id, name, description, vpc_id=vpc_id) self.groups[group_id] = group return group diff --git a/moto/ec2/responses/security_groups.py b/moto/ec2/responses/security_groups.py index 69d32d5fb..16905104f 100644 --- a/moto/ec2/responses/security_groups.py +++ b/moto/ec2/responses/security_groups.py @@ -31,7 +31,8 @@ class SecurityGroups(object): def create_security_group(self): name = self.querystring.get('GroupName')[0] description = self.querystring.get('GroupDescription')[0] - group = ec2_backend.create_security_group(name, description) + vpc_id = self.querystring.get("VpcId", [None])[0] + group = ec2_backend.create_security_group(name, description, vpc_id=vpc_id) if not group: # There was an exisitng group return "There was an existing security group with name {0}".format(name), dict(status=409) @@ -83,7 +84,7 @@ DESCRIBE_SECURITY_GROUPS_RESPONSE = """ {% for rule in group.ingress_rules %} From 9cbac9bbf4253f87e3f657f9d828b650f7a86c39 Mon Sep 17 00:00:00 2001 From: Jon Haddad Date: Wed, 30 Oct 2013 20:11:15 -0700 Subject: [PATCH 04/35] reworked internals, groups is now a dict of dicts. need to fix errors coming back from revoking and deleting groups --- moto/ec2/models.py | 35 +++++++++++++------------- tests/test_ec2/test_security_groups.py | 19 ++++++++++++-- 2 files changed, 35 insertions(+), 19 deletions(-) diff --git a/moto/ec2/models.py b/moto/ec2/models.py index 95903b82f..20d5af1c4 100644 --- a/moto/ec2/models.py +++ b/moto/ec2/models.py @@ -1,4 +1,5 @@ import copy +import itertools from collections import defaultdict from boto.ec2.instance import Instance as BotoInstance, Reservation @@ -312,36 +313,36 @@ class SecurityGroup(object): class SecurityGroupBackend(object): def __init__(self): - self.groups = {} - self.vpc_groups = {} - + # the key in the dict group is the vpc_id or None (non-vpc) + self.groups = defaultdict(dict) super(SecurityGroupBackend, self).__init__() def create_security_group(self, name, description, vpc_id=None, force=False): group_id = random_security_group_id() if not force: - existing_group = self.get_security_group_from_name(name) + existing_group = self.get_security_group_from_name(name, vpc_id) if existing_group: return None group = SecurityGroup(group_id, name, description, vpc_id=vpc_id) - self.groups[group_id] = group + + self.groups[vpc_id][group_id] = group return group def describe_security_groups(self): - return self.groups.values() + return itertools.chain(*[x.values() for x in self.groups.values()]) - def delete_security_group(self, name_or_group_id): - if name_or_group_id in self.groups: + def delete_security_group(self, name_or_group_id, vpc_id): + if name_or_group_id in self.groups[vpc_id]: # Group Id - return self.groups.pop(name_or_group_id) + return self.groups[vpc_id].pop(name_or_group_id) else: # Group Name - group = self.get_security_group_from_name(name_or_group_id) + group = self.get_security_group_from_name(name_or_group_id, vpc_id) if group: - return self.groups.pop(group.id) + return self.groups[vpc_id].pop(group.id) - def get_security_group_from_name(self, name): - for group_id, group in self.groups.iteritems(): + def get_security_group_from_name(self, name, vpc_id): + for group_id, group in self.groups[vpc_id].iteritems(): if group.name == name: return group @@ -350,16 +351,16 @@ class SecurityGroupBackend(object): default_group = ec2_backend.create_security_group("default", "The default security group", force=True) return default_group - def authorize_security_group_ingress(self, group_name, ip_protocol, from_port, to_port, ip_ranges=None, source_group_names=None): - group = self.get_security_group_from_name(group_name) + def authorize_security_group_ingress(self, group_name, ip_protocol, from_port, to_port, ip_ranges=None, source_group_names=None, vpc_id=None): + group = self.get_security_group_from_name(group_name, vpc_id) source_groups = [] for source_group_name in source_group_names: - source_groups.append(self.get_security_group_from_name(source_group_name)) + source_groups.append(self.get_security_group_from_name(source_group_name, vpc_id)) security_rule = SecurityRule(ip_protocol, from_port, to_port, ip_ranges, source_groups) group.ingress_rules.append(security_rule) - def revoke_security_group_ingress(self, group_name, ip_protocol, from_port, to_port, ip_ranges=None, source_group_names=None): + def revoke_security_group_ingress(self, group_name, ip_protocol, from_port, to_port, ip_ranges=None, source_group_names=None, vpc_id=None): group = self.get_security_group_from_name(group_name) source_groups = [] for source_group_name in source_group_names: diff --git a/tests/test_ec2/test_security_groups.py b/tests/test_ec2/test_security_groups.py index 606faebc3..6b33c3604 100644 --- a/tests/test_ec2/test_security_groups.py +++ b/tests/test_ec2/test_security_groups.py @@ -31,8 +31,8 @@ def test_create_and_describe_vpc_security_group(): security_group.name.should.equal('test security group') security_group.description.should.equal('this is a test security group') - # Trying to create another group with the same name should throw an error - conn.create_security_group.when.called_with('test security group', 'this is a test security group').should.throw(EC2ResponseError) + # Trying to create another group with the same name in the same VPC should throw an error + conn.create_security_group.when.called_with('test security group', 'this is a test security group', vpc_id).should.throw(EC2ResponseError) all_groups = conn.get_all_security_groups() @@ -41,6 +41,21 @@ def test_create_and_describe_vpc_security_group(): all_groups.should.have.length_of(1) all_groups[0].name.should.equal('test security group') +@mock_ec2 +def test_create_two_security_groups_with_same_name_in_different_vpc(): + conn = boto.connect_ec2('the_key', 'the_secret') + vpc_id = 'vpc-5300000c' + vpc_id2 = 'vpc-5300000d' + + sg1 = conn.create_security_group('test security group', 'this is a test security group', vpc_id) + sg2 = conn.create_security_group('test security group', 'this is a test security group', vpc_id2) + + all_groups = conn.get_all_security_groups() + + all_groups.should.have.length_of(2) + all_groups[0].name.should.equal('test security group') + all_groups[1].name.should.equal('test security group') + @mock_ec2 def test_deleting_security_groups(): From c2dad5357fe892ff621ecb33b62e79a271f8b9dc Mon Sep 17 00:00:00 2001 From: Jon Haddad Date: Fri, 1 Nov 2013 12:56:53 -0700 Subject: [PATCH 05/35] correctly passing vpc_id through to revoke --- moto/ec2/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/moto/ec2/models.py b/moto/ec2/models.py index 20d5af1c4..2a760dd72 100644 --- a/moto/ec2/models.py +++ b/moto/ec2/models.py @@ -361,10 +361,10 @@ class SecurityGroupBackend(object): group.ingress_rules.append(security_rule) def revoke_security_group_ingress(self, group_name, ip_protocol, from_port, to_port, ip_ranges=None, source_group_names=None, vpc_id=None): - group = self.get_security_group_from_name(group_name) + group = self.get_security_group_from_name(group_name, vpc_id) source_groups = [] for source_group_name in source_group_names: - source_groups.append(self.get_security_group_from_name(source_group_name)) + source_groups.append(self.get_security_group_from_name(source_group_name, vpc_id)) security_rule = SecurityRule(ip_protocol, from_port, to_port, ip_ranges, source_groups) if security_rule in group.ingress_rules: From 0712d40f71016d03f71f3348ebc0721a69927662 Mon Sep 17 00:00:00 2001 From: Jon Haddad Date: Thu, 5 Dec 2013 16:39:25 -0800 Subject: [PATCH 06/35] added vpc_id to deletion --- moto/ec2/responses/security_groups.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/moto/ec2/responses/security_groups.py b/moto/ec2/responses/security_groups.py index 9ec5eaa46..5e8eebaa4 100644 --- a/moto/ec2/responses/security_groups.py +++ b/moto/ec2/responses/security_groups.py @@ -42,8 +42,10 @@ class SecurityGroups(object): def delete_security_group(self): # TODO this should raise an error if there are instances in the group. See http://docs.aws.amazon.com/AWSEC2/latest/APIReference/ApiReference-query-DeleteSecurityGroup.html name = self.querystring.get('GroupName')[0] - group = ec2_backend.delete_security_group(name) + vpc_id = self.querystring.get("VpcId", [None])[0] + # needs vpc now + group = ec2_backend.delete_security_group(name, vpc_id) if not group: # There was no such group return "There was no security group with name {0}".format(name), dict(status=404) From 0e316d8fc3ad93abac78378290bd9dfdc85e7a3c Mon Sep 17 00:00:00 2001 From: Jon Haddad Date: Thu, 5 Dec 2013 16:45:18 -0800 Subject: [PATCH 07/35] fixed spot instances creation --- moto/ec2/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/moto/ec2/models.py b/moto/ec2/models.py index 2a760dd72..6121c338a 100644 --- a/moto/ec2/models.py +++ b/moto/ec2/models.py @@ -540,12 +540,12 @@ class SpotInstanceRequest(object): self.security_groups = [] if security_groups: for group_name in security_groups: - group = ec2_backend.get_security_group_from_name(group_name) + group = ec2_backend.get_security_group_from_name(group_name, None) if group: self.security_groups.append(group) else: # If not security groups, add the default - default_group = ec2_backend.get_security_group_from_name("default") + default_group = ec2_backend.get_security_group_from_name("default", None) self.security_groups.append(default_group) From 8781714f5cadeab86f96327906e1e8b68b97d78e Mon Sep 17 00:00:00 2001 From: Jon Haddad Date: Thu, 5 Dec 2013 17:00:35 -0800 Subject: [PATCH 08/35] security group deletion --- tests/test_ec2/test_security_groups.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/test_ec2/test_security_groups.py b/tests/test_ec2/test_security_groups.py index 6b33c3604..07af33152 100644 --- a/tests/test_ec2/test_security_groups.py +++ b/tests/test_ec2/test_security_groups.py @@ -76,6 +76,21 @@ def test_deleting_security_groups(): conn.delete_security_group(security_group1.id) conn.get_all_security_groups().should.have.length_of(0) +@mock_ec2 +def test_delete_security_group_in_vpc(): + conn = boto.connect_ec2('the_key', 'the_secret') + vpc_id = "vpc-12345" + security_group1 = conn.create_security_group('test1', 'test1', vpc_id) + + # Deleting a group that doesn't exist in the VPC should throw an error + conn.delete_security_group.when.called_with('test1').should.throw(EC2ResponseError) + + conn.delete_security_group("test1", vpc_id=vpc_id) + + # Delete by group id + # conn.delete_security_group(security_group1.id) + # conn.get_all_security_groups().should.have.length_of(0) + @mock_ec2 def test_authorize_ip_range_and_revoke(): From 95acab1dcae38ecae8ce4879b15a0ffda4dbf5e4 Mon Sep 17 00:00:00 2001 From: Jon Haddad Date: Thu, 5 Dec 2013 17:11:10 -0800 Subject: [PATCH 09/35] added myself to contributors --- AUTHORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.md b/AUTHORS.md index 8a0831dcd..1a21fcc35 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -12,3 +12,4 @@ Moto is written by Steve Pulec with contributions from: * [Konstantinos Koukopoulos](https://github.com/kouk) * [attili](https://github.com/attili) * [JJ Zeng](https://github.com/jjofseattle) +* [Jon Haddad](https://github.com/rustyrazorblade) From 1eac424bf52fb6fab27f74c3a68c8d8b675badec Mon Sep 17 00:00:00 2001 From: Jon Haddad Date: Thu, 5 Dec 2013 17:56:46 -0800 Subject: [PATCH 10/35] missed a commit --- tests/test_ec2/test_security_groups.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_ec2/test_security_groups.py b/tests/test_ec2/test_security_groups.py index 07af33152..56bf6a0a3 100644 --- a/tests/test_ec2/test_security_groups.py +++ b/tests/test_ec2/test_security_groups.py @@ -24,7 +24,7 @@ def test_create_and_describe_security_group(): def test_create_and_describe_vpc_security_group(): conn = boto.connect_ec2('the_key', 'the_secret') vpc_id = 'vpc-5300000c' - security_group = conn.create_security_group('test security group', 'this is a test security group', vpc_id) + security_group = conn.create_security_group('test security group', 'this is a test security group', vpc_id=vpc_id) security_group.vpc_id.should.equal(vpc_id) @@ -85,6 +85,7 @@ def test_delete_security_group_in_vpc(): # Deleting a group that doesn't exist in the VPC should throw an error conn.delete_security_group.when.called_with('test1').should.throw(EC2ResponseError) + # this should not throw an exception conn.delete_security_group("test1", vpc_id=vpc_id) # Delete by group id From 48ee4b600b14d0220079b6d9356f68e79031e09e Mon Sep 17 00:00:00 2001 From: Jon Haddad Date: Fri, 6 Dec 2013 14:34:13 -0800 Subject: [PATCH 11/35] updated SC methods to work with a group_id, which must be used if it's a group in a VPC --- moto/ec2/models.py | 18 ++++++++++-------- moto/ec2/responses/security_groups.py | 13 +++++++++---- tests/test_ec2/test_security_groups.py | 11 ++--------- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/moto/ec2/models.py b/moto/ec2/models.py index 6121c338a..a8e29bf2d 100644 --- a/moto/ec2/models.py +++ b/moto/ec2/models.py @@ -331,15 +331,17 @@ class SecurityGroupBackend(object): def describe_security_groups(self): return itertools.chain(*[x.values() for x in self.groups.values()]) - def delete_security_group(self, name_or_group_id, vpc_id): - if name_or_group_id in self.groups[vpc_id]: - # Group Id - return self.groups[vpc_id].pop(name_or_group_id) - else: - # Group Name - group = self.get_security_group_from_name(name_or_group_id, vpc_id) + def delete_security_group(self, name=None, group_id=None): + if group_id: + # loop over all the SGs, find the right one + for vpc in self.groups.values(): + if group_id in vpc: + return vpc.pop(group_id) + elif name: + # Group Name. Has to be in standard EC2, VPC needs to be identified by group_id + group = self.get_security_group_from_name(name, None) if group: - return self.groups[vpc_id].pop(group.id) + return self.groups[None].pop(group.id) def get_security_group_from_name(self, name, vpc_id): for group_id, group in self.groups[vpc_id].iteritems(): diff --git a/moto/ec2/responses/security_groups.py b/moto/ec2/responses/security_groups.py index 5e8eebaa4..1abda915a 100644 --- a/moto/ec2/responses/security_groups.py +++ b/moto/ec2/responses/security_groups.py @@ -41,11 +41,16 @@ class SecurityGroups(object): def delete_security_group(self): # TODO this should raise an error if there are instances in the group. See http://docs.aws.amazon.com/AWSEC2/latest/APIReference/ApiReference-query-DeleteSecurityGroup.html - name = self.querystring.get('GroupName')[0] - vpc_id = self.querystring.get("VpcId", [None])[0] - # needs vpc now - group = ec2_backend.delete_security_group(name, vpc_id) + name = self.querystring.get('GroupName') + sg_id = self.querystring.get('GroupId') + + if name: + group = ec2_backend.delete_security_group(name[0]) + elif sg_id: + group = ec2_backend.delete_security_group(group_id=sg_id[0]) + + # needs name or group now if not group: # There was no such group return "There was no security group with name {0}".format(name), dict(status=404) diff --git a/tests/test_ec2/test_security_groups.py b/tests/test_ec2/test_security_groups.py index 56bf6a0a3..9b1f34964 100644 --- a/tests/test_ec2/test_security_groups.py +++ b/tests/test_ec2/test_security_groups.py @@ -73,7 +73,7 @@ def test_deleting_security_groups(): conn.get_all_security_groups().should.have.length_of(1) # Delete by group id - conn.delete_security_group(security_group1.id) + conn.delete_security_group(group_id=security_group1.id) conn.get_all_security_groups().should.have.length_of(0) @mock_ec2 @@ -82,15 +82,8 @@ def test_delete_security_group_in_vpc(): vpc_id = "vpc-12345" security_group1 = conn.create_security_group('test1', 'test1', vpc_id) - # Deleting a group that doesn't exist in the VPC should throw an error - conn.delete_security_group.when.called_with('test1').should.throw(EC2ResponseError) - # this should not throw an exception - conn.delete_security_group("test1", vpc_id=vpc_id) - - # Delete by group id - # conn.delete_security_group(security_group1.id) - # conn.get_all_security_groups().should.have.length_of(0) + conn.delete_security_group(group_id=security_group1.id) @mock_ec2 From 95dec63a4b21a96a9e7bf571c9ba3d0ac69d63f8 Mon Sep 17 00:00:00 2001 From: Jon Haddad Date: Fri, 6 Dec 2013 14:40:00 -0800 Subject: [PATCH 12/35] added latest version of boto to travis matrix --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 037501a5b..e7fe9847c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,7 @@ python: env: matrix: #- BOTO_VERSION=2.13.3 + - BOTO_VERSION=2.19.0 - BOTO_VERSION=2.12.0 - BOTO_VERSION=2.11.0 - BOTO_VERSION=2.10.0 From 3b8cd5ad7dd677260c2e854a289ee6be9a48e112 Mon Sep 17 00:00:00 2001 From: Steve Pulec Date: Thu, 19 Dec 2013 16:16:00 -0500 Subject: [PATCH 13/35] Exclude tests from packagin --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 4a5806ca9..7b32ebbe5 100644 --- a/setup.py +++ b/setup.py @@ -28,6 +28,6 @@ setup( 'moto_server = moto.server:main', ], }, - packages=find_packages(), + packages=find_packages(exclude=("tests", "tests.*")), install_requires=install_requires, ) From 748fa52cca843bdee6468ebb14b62f6020b23a9b Mon Sep 17 00:00:00 2001 From: Andres Riancho Date: Thu, 26 Dec 2013 13:12:50 -0300 Subject: [PATCH 14/35] Fix for https://github.com/spulec/moto/issues/72 Need to run the whole test suite to make sure no regressions were added --- moto/core/models.py | 15 ++++++++++++--- tests/test_core/test_nested.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 tests/test_core/test_nested.py diff --git a/moto/core/models.py b/moto/core/models.py index 17238fcb0..dd1f37947 100644 --- a/moto/core/models.py +++ b/moto/core/models.py @@ -7,9 +7,13 @@ from .utils import convert_regex_to_flask_path class MockAWS(object): + nested_count = 0 + def __init__(self, backend): self.backend = backend - HTTPretty.reset() + + if self.__class__.nested_count == 0: + HTTPretty.reset() def __call__(self, func): return self.decorate_callable(func) @@ -22,7 +26,9 @@ class MockAWS(object): def start(self): self.backend.reset() - HTTPretty.enable() + + if not HTTPretty.is_enabled(): + HTTPretty.enable() for method in HTTPretty.METHODS: for key, value in self.backend.urls.iteritems(): @@ -40,7 +46,10 @@ class MockAWS(object): ) def stop(self): - HTTPretty.disable() + self.__class__.nested_count -= 1 + + if self.__class__.nested_count == 0: + HTTPretty.disable() def decorate_callable(self, func): def wrapper(*args, **kwargs): diff --git a/tests/test_core/test_nested.py b/tests/test_core/test_nested.py new file mode 100644 index 000000000..9ae366a50 --- /dev/null +++ b/tests/test_core/test_nested.py @@ -0,0 +1,28 @@ +import unittest + +from boto.sqs.connection import SQSConnection +from boto.sqs.message import Message +from boto.ec2 import EC2Connection + +from moto import mock_sqs, mock_ec2 + + +class TestNestedDecorators(unittest.TestCase): + + @mock_sqs + def setup_sqs_queue(self): + conn = SQSConnection() + q = conn.create_queue('some-queue') + + m = Message() + m.set_body('This is my first message.') + q.write(m) + + self.assertEqual(q.count(), 1) + + @mock_ec2 + def test_nested(self): + self.setup_sqs_queue() + + conn = EC2Connection() + conn.run_instances('ami-123456') \ No newline at end of file From b331e8ee8e0f4fbf8b76677b9630ee3b11e36aab Mon Sep 17 00:00:00 2001 From: Andres Riancho Date: Thu, 26 Dec 2013 13:42:41 -0300 Subject: [PATCH 15/35] Ignore pycharm metadata --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index e17800a77..7d3471a59 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ dist/* *.pyc .noseids build/ +.idea/ From f843a1bba9f0016331c7ca0994d51fc04effe1d7 Mon Sep 17 00:00:00 2001 From: Andres Riancho Date: Thu, 26 Dec 2013 13:43:18 -0300 Subject: [PATCH 16/35] Ignore backup files --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 7d3471a59..923d72edd 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,8 @@ dist/* .tox .coverage *.pyc +*~ .noseids build/ .idea/ + From 186b40de54e3b294b2ee81c87c44acf5c25b1c17 Mon Sep 17 00:00:00 2001 From: Andres Riancho Date: Thu, 26 Dec 2013 13:52:03 -0300 Subject: [PATCH 17/35] All tests pass! Fixes https://github.com/spulec/moto/issues/72 --- moto/core/models.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/moto/core/models.py b/moto/core/models.py index dd1f37947..e3777741f 100644 --- a/moto/core/models.py +++ b/moto/core/models.py @@ -25,6 +25,7 @@ class MockAWS(object): self.stop() def start(self): + self.__class__.nested_count += 1 self.backend.reset() if not HTTPretty.is_enabled(): @@ -48,6 +49,9 @@ class MockAWS(object): def stop(self): self.__class__.nested_count -= 1 + if self.__class__.nested_count < 0: + raise RuntimeError('Called stop() before start().') + if self.__class__.nested_count == 0: HTTPretty.disable() From d58f406de4c5adcf482ba0ee1e4a91a3b02ed3dc Mon Sep 17 00:00:00 2001 From: Andres Riancho Date: Fri, 27 Dec 2013 12:26:25 -0300 Subject: [PATCH 18/35] Fixes https://github.com/spulec/moto/issues/74 --- moto/core/responses.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/moto/core/responses.py b/moto/core/responses.py index 3d8fa138c..cef0dad5c 100644 --- a/moto/core/responses.py +++ b/moto/core/responses.py @@ -9,18 +9,30 @@ from moto.core.utils import camelcase_to_underscores, method_names_from_class class BaseResponse(object): def dispatch(self, request, full_url, headers): + querystring = None + if hasattr(request, 'body'): # Boto self.body = request.body else: # Flask server + + # FIXME: At least in Flask==0.10.1, request.data is an empty string + # and the information we want is in request.form. Keeping self.body + # definition for back-compatibility self.body = request.data - querystring = parse_qs(urlparse(full_url).query) - if not querystring: - querystring = parse_qs(self.body) - if not querystring: - querystring = headers + querystring = {} + for key, value in request.form.iteritems(): + querystring[key] = [value,] + + + if querystring is None: + querystring = parse_qs(urlparse(full_url).query) + if not querystring: + querystring = parse_qs(self.body) + if not querystring: + querystring = headers self.uri = full_url self.path = urlparse(full_url).path From 2073dc769a8c1902dee62ef4df22dec4c3394cfe Mon Sep 17 00:00:00 2001 From: Steve Pulec Date: Sat, 28 Dec 2013 20:15:37 -0500 Subject: [PATCH 19/35] Clean up querystring logic. --- moto/core/responses.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/moto/core/responses.py b/moto/core/responses.py index cef0dad5c..b71ad0d6e 100644 --- a/moto/core/responses.py +++ b/moto/core/responses.py @@ -24,15 +24,14 @@ class BaseResponse(object): querystring = {} for key, value in request.form.iteritems(): - querystring[key] = [value,] + querystring[key] = [value, ] - - if querystring is None: + if not querystring: querystring = parse_qs(urlparse(full_url).query) - if not querystring: - querystring = parse_qs(self.body) - if not querystring: - querystring = headers + if not querystring: + querystring = parse_qs(self.body) + if not querystring: + querystring = headers self.uri = full_url self.path = urlparse(full_url).path From fefc12457e77fbad765ca0b08595c6dffd13afe1 Mon Sep 17 00:00:00 2001 From: Steve Pulec Date: Sat, 28 Dec 2013 20:19:01 -0500 Subject: [PATCH 20/35] 0.2.12 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 7b32ebbe5..9fa9c2302 100644 --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ if sys.version_info < (2, 7): setup( name='moto', - version='0.2.11', + version='0.2.12', description='A library that allows your python tests to easily' ' mock out the boto library', author='Steve Pulec', From ec938ef90490fcf4db1e4ef64338b314a93ea8f1 Mon Sep 17 00:00:00 2001 From: Steve Pulec Date: Sun, 29 Dec 2013 08:25:13 -0500 Subject: [PATCH 21/35] Convert all EC2Response Mixins to subclasses. --- moto/ec2/responses/amis.py | 3 ++- moto/ec2/responses/availability_zones_and_regions.py | 3 ++- moto/ec2/responses/elastic_block_store.py | 3 ++- moto/ec2/responses/elastic_ip_addresses.py | 4 ++-- moto/ec2/responses/general.py | 3 ++- moto/ec2/responses/instances.py | 3 ++- moto/ec2/responses/security_groups.py | 3 ++- moto/ec2/responses/spot_instances.py | 3 ++- moto/ec2/responses/subnets.py | 3 ++- moto/ec2/responses/tags.py | 3 ++- moto/ec2/responses/vpcs.py | 3 ++- 11 files changed, 22 insertions(+), 12 deletions(-) diff --git a/moto/ec2/responses/amis.py b/moto/ec2/responses/amis.py index 10936e635..74375e1b0 100644 --- a/moto/ec2/responses/amis.py +++ b/moto/ec2/responses/amis.py @@ -1,10 +1,11 @@ from jinja2 import Template +from moto.core.responses import BaseResponse from moto.ec2.models import ec2_backend from moto.ec2.utils import instance_ids_from_querystring, image_ids_from_querystring -class AmisResponse(object): +class AmisResponse(BaseResponse): def create_image(self): name = self.querystring.get('Name')[0] if "Description" in self.querystring: diff --git a/moto/ec2/responses/availability_zones_and_regions.py b/moto/ec2/responses/availability_zones_and_regions.py index f216a644f..1e1b482aa 100644 --- a/moto/ec2/responses/availability_zones_and_regions.py +++ b/moto/ec2/responses/availability_zones_and_regions.py @@ -1,9 +1,10 @@ from jinja2 import Template +from moto.core.responses import BaseResponse from moto.ec2.models import ec2_backend -class AvailabilityZonesAndRegions(object): +class AvailabilityZonesAndRegions(BaseResponse): def describe_availability_zones(self): zones = ec2_backend.describe_availability_zones() template = Template(DESCRIBE_ZONES_RESPONSE) diff --git a/moto/ec2/responses/elastic_block_store.py b/moto/ec2/responses/elastic_block_store.py index 64ad65b30..b179ad13a 100644 --- a/moto/ec2/responses/elastic_block_store.py +++ b/moto/ec2/responses/elastic_block_store.py @@ -1,9 +1,10 @@ from jinja2 import Template +from moto.core.responses import BaseResponse from moto.ec2.models import ec2_backend -class ElasticBlockStore(object): +class ElasticBlockStore(BaseResponse): def attach_volume(self): volume_id = self.querystring.get('VolumeId')[0] instance_id = self.querystring.get('InstanceId')[0] diff --git a/moto/ec2/responses/elastic_ip_addresses.py b/moto/ec2/responses/elastic_ip_addresses.py index 60cdbcf5e..5553ef956 100644 --- a/moto/ec2/responses/elastic_ip_addresses.py +++ b/moto/ec2/responses/elastic_ip_addresses.py @@ -1,11 +1,11 @@ from jinja2 import Template +from moto.core.responses import BaseResponse from moto.ec2.models import ec2_backend from moto.ec2.utils import sequence_from_querystring - -class ElasticIPAddresses(object): +class ElasticIPAddresses(BaseResponse): def allocate_address(self): if "Domain" in self.querystring: domain = self.querystring.get('Domain')[0] diff --git a/moto/ec2/responses/general.py b/moto/ec2/responses/general.py index 5353bb99a..77636655f 100644 --- a/moto/ec2/responses/general.py +++ b/moto/ec2/responses/general.py @@ -1,10 +1,11 @@ from jinja2 import Template +from moto.core.responses import BaseResponse from moto.ec2.models import ec2_backend from moto.ec2.utils import instance_ids_from_querystring -class General(object): +class General(BaseResponse): def get_console_output(self): self.instance_ids = instance_ids_from_querystring(self.querystring) instance_id = self.instance_ids[0] diff --git a/moto/ec2/responses/instances.py b/moto/ec2/responses/instances.py index f230dcf49..85b2b4874 100644 --- a/moto/ec2/responses/instances.py +++ b/moto/ec2/responses/instances.py @@ -1,12 +1,13 @@ from jinja2 import Template +from moto.core.responses import BaseResponse from moto.core.utils import camelcase_to_underscores from moto.ec2.models import ec2_backend from moto.ec2.utils import instance_ids_from_querystring, filters_from_querystring, filter_reservations from moto.ec2.exceptions import InvalidIdError -class InstanceResponse(object): +class InstanceResponse(BaseResponse): def describe_instances(self): instance_ids = instance_ids_from_querystring(self.querystring) if instance_ids: diff --git a/moto/ec2/responses/security_groups.py b/moto/ec2/responses/security_groups.py index 1abda915a..163faa1d9 100644 --- a/moto/ec2/responses/security_groups.py +++ b/moto/ec2/responses/security_groups.py @@ -1,5 +1,6 @@ from jinja2 import Template +from moto.core.responses import BaseResponse from moto.ec2.models import ec2_backend @@ -20,7 +21,7 @@ def process_rules_from_querystring(querystring): return (name, ip_protocol, from_port, to_port, ip_ranges, source_groups) -class SecurityGroups(object): +class SecurityGroups(BaseResponse): def authorize_security_group_egress(self): raise NotImplementedError('SecurityGroups.authorize_security_group_egress is not yet implemented') diff --git a/moto/ec2/responses/spot_instances.py b/moto/ec2/responses/spot_instances.py index 759914989..814bbcbd6 100644 --- a/moto/ec2/responses/spot_instances.py +++ b/moto/ec2/responses/spot_instances.py @@ -1,9 +1,10 @@ from jinja2 import Template +from moto.core.responses import BaseResponse from moto.ec2.models import ec2_backend -class SpotInstances(object): +class SpotInstances(BaseResponse): def _get_param(self, param_name): return self.querystring.get(param_name, [None])[0] diff --git a/moto/ec2/responses/subnets.py b/moto/ec2/responses/subnets.py index 761f492e5..754982214 100644 --- a/moto/ec2/responses/subnets.py +++ b/moto/ec2/responses/subnets.py @@ -1,9 +1,10 @@ from jinja2 import Template +from moto.core.responses import BaseResponse from moto.ec2.models import ec2_backend -class Subnets(object): +class Subnets(BaseResponse): def create_subnet(self): vpc_id = self.querystring.get('VpcId')[0] cidr_block = self.querystring.get('CidrBlock')[0] diff --git a/moto/ec2/responses/tags.py b/moto/ec2/responses/tags.py index dd8dce8e8..6a12cf00c 100644 --- a/moto/ec2/responses/tags.py +++ b/moto/ec2/responses/tags.py @@ -1,10 +1,11 @@ from jinja2 import Template +from moto.core.responses import BaseResponse from moto.ec2.models import ec2_backend from moto.ec2.utils import resource_ids_from_querystring -class TagResponse(object): +class TagResponse(BaseResponse): def create_tags(self): resource_ids = resource_ids_from_querystring(self.querystring) diff --git a/moto/ec2/responses/vpcs.py b/moto/ec2/responses/vpcs.py index c2b16f9cd..1decdb33c 100644 --- a/moto/ec2/responses/vpcs.py +++ b/moto/ec2/responses/vpcs.py @@ -1,9 +1,10 @@ from jinja2 import Template +from moto.core.responses import BaseResponse from moto.ec2.models import ec2_backend -class VPCs(object): +class VPCs(BaseResponse): def create_vpc(self): cidr_block = self.querystring.get('CidrBlock')[0] vpc = ec2_backend.create_vpc(cidr_block) From f3890ffc3196410255ccc6df7505857c8a1aec91 Mon Sep 17 00:00:00 2001 From: Steve Pulec Date: Sun, 29 Dec 2013 08:34:07 -0500 Subject: [PATCH 22/35] Fix for EC2Response MRO --- moto/ec2/responses/__init__.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/moto/ec2/responses/__init__.py b/moto/ec2/responses/__init__.py index 690419438..cebff3eba 100644 --- a/moto/ec2/responses/__init__.py +++ b/moto/ec2/responses/__init__.py @@ -1,5 +1,3 @@ -from moto.core.responses import BaseResponse - from .amazon_dev_pay import AmazonDevPay from .amis import AmisResponse from .availability_zones_and_regions import AvailabilityZonesAndRegions @@ -31,7 +29,6 @@ from .windows import Windows class EC2Response( - BaseResponse, AmazonDevPay, AmisResponse, AvailabilityZonesAndRegions, From 74e2c198654744f7bf8cd7ee08cd91a6471de5f3 Mon Sep 17 00:00:00 2001 From: Steve Pulec Date: Sun, 29 Dec 2013 08:40:38 -0500 Subject: [PATCH 23/35] Cleanup unused imports --- moto/ec2/responses/amazon_dev_pay.py | 7 ++----- moto/ec2/responses/customer_gateways.py | 7 ++----- moto/ec2/responses/dhcp_options.py | 7 ++----- moto/ec2/responses/elastic_network_interfaces.py | 7 ++----- moto/ec2/responses/internet_gateways.py | 7 ++----- moto/ec2/responses/ip_addresses.py | 7 ++----- moto/ec2/responses/key_pairs.py | 7 ++----- moto/ec2/responses/monitoring.py | 7 ++----- moto/ec2/responses/network_acls.py | 7 ++----- moto/ec2/responses/placement_groups.py | 7 ++----- moto/ec2/responses/reserved_instances.py | 7 ++----- moto/ec2/responses/route_tables.py | 7 ++----- moto/ec2/responses/virtual_private_gateways.py | 7 ++----- moto/ec2/responses/vm_export.py | 7 ++----- moto/ec2/responses/vm_import.py | 7 ++----- moto/ec2/responses/vpn_connections.py | 7 ++----- moto/ec2/responses/windows.py | 7 ++----- moto/s3bucket_path/models.py | 2 +- moto/sqs/models.py | 2 +- 19 files changed, 36 insertions(+), 87 deletions(-) diff --git a/moto/ec2/responses/amazon_dev_pay.py b/moto/ec2/responses/amazon_dev_pay.py index b5a0cf646..d0ef8893b 100644 --- a/moto/ec2/responses/amazon_dev_pay.py +++ b/moto/ec2/responses/amazon_dev_pay.py @@ -1,9 +1,6 @@ -from jinja2 import Template - -from moto.ec2.models import ec2_backend -from moto.ec2.utils import resource_ids_from_querystring +from moto.core.responses import BaseResponse -class AmazonDevPay(object): +class AmazonDevPay(BaseResponse): def confirm_product_instance(self): raise NotImplementedError('AmazonDevPay.confirm_product_instance is not yet implemented') diff --git a/moto/ec2/responses/customer_gateways.py b/moto/ec2/responses/customer_gateways.py index 06c590e4e..1f30a9d16 100644 --- a/moto/ec2/responses/customer_gateways.py +++ b/moto/ec2/responses/customer_gateways.py @@ -1,10 +1,7 @@ -from jinja2 import Template - -from moto.ec2.models import ec2_backend -from moto.ec2.utils import resource_ids_from_querystring +from moto.core.responses import BaseResponse -class CustomerGateways(object): +class CustomerGateways(BaseResponse): def create_customer_gateway(self): raise NotImplementedError('CustomerGateways(AmazonVPC).create_customer_gateway is not yet implemented') diff --git a/moto/ec2/responses/dhcp_options.py b/moto/ec2/responses/dhcp_options.py index 23e73b317..f94abd9be 100644 --- a/moto/ec2/responses/dhcp_options.py +++ b/moto/ec2/responses/dhcp_options.py @@ -1,10 +1,7 @@ -from jinja2 import Template - -from moto.ec2.models import ec2_backend -from moto.ec2.utils import resource_ids_from_querystring +from moto.core.responses import BaseResponse -class DHCPOptions(object): +class DHCPOptions(BaseResponse): def associate_dhcp_options(self): raise NotImplementedError('DHCPOptions(AmazonVPC).associate_dhcp_options is not yet implemented') diff --git a/moto/ec2/responses/elastic_network_interfaces.py b/moto/ec2/responses/elastic_network_interfaces.py index f3624070d..985fc334c 100644 --- a/moto/ec2/responses/elastic_network_interfaces.py +++ b/moto/ec2/responses/elastic_network_interfaces.py @@ -1,10 +1,7 @@ -from jinja2 import Template - -from moto.ec2.models import ec2_backend -from moto.ec2.utils import resource_ids_from_querystring +from moto.core.responses import BaseResponse -class ElasticNetworkInterfaces(object): +class ElasticNetworkInterfaces(BaseResponse): def attach_network_interface(self): raise NotImplementedError('ElasticNetworkInterfaces(AmazonVPC).attach_network_interface is not yet implemented') diff --git a/moto/ec2/responses/internet_gateways.py b/moto/ec2/responses/internet_gateways.py index 772623bc6..c9b24922f 100644 --- a/moto/ec2/responses/internet_gateways.py +++ b/moto/ec2/responses/internet_gateways.py @@ -1,10 +1,7 @@ -from jinja2 import Template - -from moto.ec2.models import ec2_backend -from moto.ec2.utils import resource_ids_from_querystring +from moto.core.responses import BaseResponse -class InternetGateways(object): +class InternetGateways(BaseResponse): def attach_internet_gateway(self): raise NotImplementedError('InternetGateways(AmazonVPC).attach_internet_gateway is not yet implemented') diff --git a/moto/ec2/responses/ip_addresses.py b/moto/ec2/responses/ip_addresses.py index b88a5462e..7d79d2a75 100644 --- a/moto/ec2/responses/ip_addresses.py +++ b/moto/ec2/responses/ip_addresses.py @@ -1,10 +1,7 @@ -from jinja2 import Template - -from moto.ec2.models import ec2_backend -from moto.ec2.utils import resource_ids_from_querystring +from moto.core.responses import BaseResponse -class IPAddresses(object): +class IPAddresses(BaseResponse): def assign_private_ip_addresses(self): raise NotImplementedError('IPAddresses.assign_private_ip_addresses is not yet implemented') diff --git a/moto/ec2/responses/key_pairs.py b/moto/ec2/responses/key_pairs.py index 23e438bfc..fd0a35fe6 100644 --- a/moto/ec2/responses/key_pairs.py +++ b/moto/ec2/responses/key_pairs.py @@ -1,10 +1,7 @@ -from jinja2 import Template - -from moto.ec2.models import ec2_backend -from moto.ec2.utils import resource_ids_from_querystring +from moto.core.responses import BaseResponse -class KeyPairs(object): +class KeyPairs(BaseResponse): def create_key_pair(self): raise NotImplementedError('KeyPairs.create_key_pair is not yet implemented') diff --git a/moto/ec2/responses/monitoring.py b/moto/ec2/responses/monitoring.py index e3fc1611a..40a2a7434 100644 --- a/moto/ec2/responses/monitoring.py +++ b/moto/ec2/responses/monitoring.py @@ -1,10 +1,7 @@ -from jinja2 import Template - -from moto.ec2.models import ec2_backend -from moto.ec2.utils import resource_ids_from_querystring +from moto.core.responses import BaseResponse -class Monitoring(object): +class Monitoring(BaseResponse): def monitor_instances(self): raise NotImplementedError('Monitoring.monitor_instances is not yet implemented') diff --git a/moto/ec2/responses/network_acls.py b/moto/ec2/responses/network_acls.py index abcf1ab6e..56ac36c38 100644 --- a/moto/ec2/responses/network_acls.py +++ b/moto/ec2/responses/network_acls.py @@ -1,10 +1,7 @@ -from jinja2 import Template - -from moto.ec2.models import ec2_backend -from moto.ec2.utils import resource_ids_from_querystring +from moto.core.responses import BaseResponse -class NetworkACLs(object): +class NetworkACLs(BaseResponse): def create_network_acl(self): raise NotImplementedError('NetworkACLs(AmazonVPC).create_network_acl is not yet implemented') diff --git a/moto/ec2/responses/placement_groups.py b/moto/ec2/responses/placement_groups.py index 7f7b881df..2260b8852 100644 --- a/moto/ec2/responses/placement_groups.py +++ b/moto/ec2/responses/placement_groups.py @@ -1,10 +1,7 @@ -from jinja2 import Template - -from moto.ec2.models import ec2_backend -from moto.ec2.utils import resource_ids_from_querystring +from moto.core.responses import BaseResponse -class PlacementGroups(object): +class PlacementGroups(BaseResponse): def create_placement_group(self): raise NotImplementedError('PlacementGroups.create_placement_group is not yet implemented') diff --git a/moto/ec2/responses/reserved_instances.py b/moto/ec2/responses/reserved_instances.py index faf413426..bab7967fd 100644 --- a/moto/ec2/responses/reserved_instances.py +++ b/moto/ec2/responses/reserved_instances.py @@ -1,10 +1,7 @@ -from jinja2 import Template - -from moto.ec2.models import ec2_backend -from moto.ec2.utils import resource_ids_from_querystring +from moto.core.responses import BaseResponse -class ReservedInstances(object): +class ReservedInstances(BaseResponse): def cancel_reserved_instances_listing(self): raise NotImplementedError('ReservedInstances.cancel_reserved_instances_listing is not yet implemented') diff --git a/moto/ec2/responses/route_tables.py b/moto/ec2/responses/route_tables.py index 6266742ed..b9dcfcceb 100644 --- a/moto/ec2/responses/route_tables.py +++ b/moto/ec2/responses/route_tables.py @@ -1,10 +1,7 @@ -from jinja2 import Template - -from moto.ec2.models import ec2_backend -from moto.ec2.utils import resource_ids_from_querystring +from moto.core.responses import BaseResponse -class RouteTables(object): +class RouteTables(BaseResponse): def associate_route_table(self): raise NotImplementedError('RouteTables(AmazonVPC).associate_route_table is not yet implemented') diff --git a/moto/ec2/responses/virtual_private_gateways.py b/moto/ec2/responses/virtual_private_gateways.py index 7f4c8292d..e2bdac7db 100644 --- a/moto/ec2/responses/virtual_private_gateways.py +++ b/moto/ec2/responses/virtual_private_gateways.py @@ -1,10 +1,7 @@ -from jinja2 import Template - -from moto.ec2.models import ec2_backend -from moto.ec2.utils import resource_ids_from_querystring +from moto.core.responses import BaseResponse -class VirtualPrivateGateways(object): +class VirtualPrivateGateways(BaseResponse): def attach_vpn_gateway(self): raise NotImplementedError('VirtualPrivateGateways(AmazonVPC).attach_vpn_gateway is not yet implemented') diff --git a/moto/ec2/responses/vm_export.py b/moto/ec2/responses/vm_export.py index 466c627a6..8a88a709d 100644 --- a/moto/ec2/responses/vm_export.py +++ b/moto/ec2/responses/vm_export.py @@ -1,10 +1,7 @@ -from jinja2 import Template - -from moto.ec2.models import ec2_backend -from moto.ec2.utils import resource_ids_from_querystring +from moto.core.responses import BaseResponse -class VMExport(object): +class VMExport(BaseResponse): def cancel_export_task(self): raise NotImplementedError('VMExport.cancel_export_task is not yet implemented') diff --git a/moto/ec2/responses/vm_import.py b/moto/ec2/responses/vm_import.py index 7bed7c8da..a30cf706c 100644 --- a/moto/ec2/responses/vm_import.py +++ b/moto/ec2/responses/vm_import.py @@ -1,10 +1,7 @@ -from jinja2 import Template - -from moto.ec2.models import ec2_backend -from moto.ec2.utils import resource_ids_from_querystring +from moto.core.responses import BaseResponse -class VMImport(object): +class VMImport(BaseResponse): def cancel_conversion_task(self): raise NotImplementedError('VMImport.cancel_conversion_task is not yet implemented') diff --git a/moto/ec2/responses/vpn_connections.py b/moto/ec2/responses/vpn_connections.py index 1a4dbb75a..61c34336b 100644 --- a/moto/ec2/responses/vpn_connections.py +++ b/moto/ec2/responses/vpn_connections.py @@ -1,10 +1,7 @@ -from jinja2 import Template - -from moto.ec2.models import ec2_backend -from moto.ec2.utils import resource_ids_from_querystring +from moto.core.responses import BaseResponse -class VPNConnections(object): +class VPNConnections(BaseResponse): def create_vpn_connection(self): raise NotImplementedError('VPNConnections(AmazonVPC).create_vpn_connection is not yet implemented') diff --git a/moto/ec2/responses/windows.py b/moto/ec2/responses/windows.py index 4c600c9a3..809c82a1c 100644 --- a/moto/ec2/responses/windows.py +++ b/moto/ec2/responses/windows.py @@ -1,10 +1,7 @@ -from jinja2 import Template - -from moto.ec2.models import ec2_backend -from moto.ec2.utils import resource_ids_from_querystring +from moto.core.responses import BaseResponse -class Windows(object): +class Windows(BaseResponse): def bundle_instance(self): raise NotImplementedError('Windows.bundle_instance is not yet implemented') diff --git a/moto/s3bucket_path/models.py b/moto/s3bucket_path/models.py index 2b7e99539..a32fe18e6 100644 --- a/moto/s3bucket_path/models.py +++ b/moto/s3bucket_path/models.py @@ -2,6 +2,6 @@ from moto.s3.models import S3Backend class S3BucketPathBackend(S3Backend): - True + pass s3bucket_path_backend = S3BucketPathBackend() diff --git a/moto/sqs/models.py b/moto/sqs/models.py index 5c6d04fe7..f82dc3208 100644 --- a/moto/sqs/models.py +++ b/moto/sqs/models.py @@ -100,7 +100,7 @@ class SQSBackend(BaseBackend): def receive_messages(self, queue_name, count): queue = self.get_queue(queue_name) result = [] - for index in range(count): + for _ in range(count): if queue.messages: result.append(queue.messages.pop(0)) return result From c1f224954e511cb76c2571449ed3a7c1c9d72a8f Mon Sep 17 00:00:00 2001 From: Steve Pulec Date: Sun, 29 Dec 2013 08:59:07 -0500 Subject: [PATCH 24/35] Cleanup some style --- moto/autoscaling/models.py | 2 ++ moto/core/responses.py | 8 +++++++- moto/dynamodb/models.py | 6 +++--- moto/ec2/models.py | 14 +++++++------- moto/ec2/responses/instances.py | 8 ++++++-- moto/emr/models.py | 4 ++-- moto/route53/models.py | 2 +- 7 files changed, 28 insertions(+), 16 deletions(-) diff --git a/moto/autoscaling/models.py b/moto/autoscaling/models.py index a367ba297..a9447974b 100644 --- a/moto/autoscaling/models.py +++ b/moto/autoscaling/models.py @@ -83,6 +83,8 @@ class FakeAutoScalingGroup(object): self.launch_config = autoscaling_backend.launch_configurations[launch_config_name] self.launch_config_name = launch_config_name self.vpc_zone_identifier = vpc_zone_identifier + self.health_check_period = health_check_period + self.health_check_type = health_check_type self.set_desired_capacity(desired_capacity) diff --git a/moto/core/responses.py b/moto/core/responses.py index b71ad0d6e..a47cf531c 100644 --- a/moto/core/responses.py +++ b/moto/core/responses.py @@ -75,7 +75,13 @@ def metadata_response(request, full_url, headers): Expiration=tomorrow.strftime("%Y-%m-%dT%H:%M:%SZ") ) - path = parsed_url.path.lstrip("/latest/meta-data/") + path = parsed_url.path + + meta_data_prefix = "/latest/meta-data/" + # Strip prefix if it is there + if path.startswith(meta_data_prefix): + path = path[len(meta_data_prefix):] + if path == '': result = 'iam' elif path == 'iam': diff --git a/moto/dynamodb/models.py b/moto/dynamodb/models.py index 198b6dc38..67f5152d6 100644 --- a/moto/dynamodb/models.py +++ b/moto/dynamodb/models.py @@ -3,10 +3,10 @@ import datetime import json try: - from collections import OrderedDict + from collections import OrderedDict except ImportError: - # python 2.6 or earlier, use backport - from ordereddict import OrderedDict + # python 2.6 or earlier, use backport + from ordereddict import OrderedDict from moto.core import BaseBackend diff --git a/moto/ec2/models.py b/moto/ec2/models.py index a8e29bf2d..4e05ceda8 100644 --- a/moto/ec2/models.py +++ b/moto/ec2/models.py @@ -36,23 +36,23 @@ class Instance(BotoInstance): self._state = InstanceState("running", 16) self.user_data = user_data - def start(self): + def start(self, *args, **kwargs): self._state.name = "running" self._state.code = 16 - def stop(self): + def stop(self, *args, **kwargs): self._state.name = "stopped" self._state.code = 80 - def terminate(self): + def terminate(self, *args, **kwargs): self._state.name = "terminated" self._state.code = 48 - def reboot(self): + def reboot(self, *args, **kwargs): self._state.name = "running" self._state.code = 16 - def get_tags(self): + def get_tags(self, *args, **kwargs): tags = ec2_backend.describe_tags(self.id) return tags @@ -562,7 +562,7 @@ class SpotRequestBackend(object): instance_type, placement, kernel_id, ramdisk_id, monitoring_enabled, subnet_id): requests = [] - for index in range(count): + for _ in range(count): spot_request_id = random_spot_request_id() request = SpotInstanceRequest( spot_request_id, price, image_id, type, valid_from, valid_until, @@ -584,7 +584,7 @@ class SpotRequestBackend(object): return requests -class ElasticAddress(): +class ElasticAddress(object): def __init__(self, domain): self.public_ip = random_ip() self.allocation_id = random_eip_allocation_id() if domain == "vpc" else None diff --git a/moto/ec2/responses/instances.py b/moto/ec2/responses/instances.py index 85b2b4874..cc2c002bf 100644 --- a/moto/ec2/responses/instances.py +++ b/moto/ec2/responses/instances.py @@ -68,12 +68,16 @@ class InstanceResponse(BaseResponse): return template.render(instance=instance, attribute=attribute, value=value) def modify_instance_attribute(self): + attribute_key = None for key, value in self.querystring.iteritems(): if '.Value' in key: + attribute_key = key break - value = self.querystring.get(key)[0] - normalized_attribute = camelcase_to_underscores(key.split(".")[0]) + if not attribute_key: + return + value = self.querystring.get(attribute_key)[0] + normalized_attribute = camelcase_to_underscores(attribute_key.split(".")[0]) instance_ids = instance_ids_from_querystring(self.querystring) instance_id = instance_ids[0] ec2_backend.modify_instance_attribute(instance_id, normalized_attribute, value) diff --git a/moto/emr/models.py b/moto/emr/models.py index 5dca3f62e..6c2045622 100644 --- a/moto/emr/models.py +++ b/moto/emr/models.py @@ -100,7 +100,7 @@ class FakeJobFlow(object): def master_instance_type(self): groups = self.instance_groups if groups: - groups[0].type + return groups[0].type else: return self.initial_master_instance_type @@ -108,7 +108,7 @@ class FakeJobFlow(object): def slave_instance_type(self): groups = self.instance_groups if groups: - groups[0].type + return groups[0].type else: return self.initial_slave_instance_type diff --git a/moto/route53/models.py b/moto/route53/models.py index d901996fa..1b2067504 100644 --- a/moto/route53/models.py +++ b/moto/route53/models.py @@ -2,7 +2,7 @@ from moto.core import BaseBackend from moto.core.utils import get_random_hex -class FakeZone: +class FakeZone(object): def __init__(self, name, id): self.name = name From 56357d344ce19e6b7aa31c95cfddcc65d75cd2f6 Mon Sep 17 00:00:00 2001 From: Andres Riancho Date: Fri, 27 Dec 2013 15:45:29 -0300 Subject: [PATCH 25/35] Making URL a little bit more strict --- moto/sqs/urls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/moto/sqs/urls.py b/moto/sqs/urls.py index 80fe27595..7e3614866 100644 --- a/moto/sqs/urls.py +++ b/moto/sqs/urls.py @@ -1,7 +1,7 @@ from .responses import QueueResponse, QueuesResponse url_bases = [ - "https?://(.*).amazonaws.com" + "https?://queue(.*).amazonaws.com" ] url_paths = { From c6515af8bf195d8c5422dcadccee7cc76d716557 Mon Sep 17 00:00:00 2001 From: Andres Riancho Date: Fri, 27 Dec 2013 15:45:53 -0300 Subject: [PATCH 26/35] Now we have a stand-alone server which can provide services for more than one backend at the same time --- moto/core/models.py | 7 +++++ moto/core/responses.py | 10 +++--- moto/server.py | 71 ++++++++++++++++++++++++++++++++---------- 3 files changed, 67 insertions(+), 21 deletions(-) diff --git a/moto/core/models.py b/moto/core/models.py index e3777741f..c5c23d155 100644 --- a/moto/core/models.py +++ b/moto/core/models.py @@ -110,6 +110,13 @@ class BaseBackend(object): return paths + @property + def url_bases(self): + """ + A list containing the url_bases extracted from urls.py + """ + return self._url_module.url_bases + @property def flask_paths(self): """ diff --git a/moto/core/responses.py b/moto/core/responses.py index a47cf531c..a39795577 100644 --- a/moto/core/responses.py +++ b/moto/core/responses.py @@ -9,7 +9,7 @@ from moto.core.utils import camelcase_to_underscores, method_names_from_class class BaseResponse(object): def dispatch(self, request, full_url, headers): - querystring = None + querystring = {} if hasattr(request, 'body'): # Boto @@ -26,12 +26,12 @@ class BaseResponse(object): for key, value in request.form.iteritems(): querystring[key] = [value, ] + if querystring is None: + querystring.update(parse_qs(urlparse(full_url).query)) if not querystring: - querystring = parse_qs(urlparse(full_url).query) + querystring.update(parse_qs(self.body)) if not querystring: - querystring = parse_qs(self.body) - if not querystring: - querystring = headers + querystring.update(headers) self.uri = full_url self.path = urlparse(full_url).path diff --git a/moto/server.py b/moto/server.py index 9ef135359..d35cbae98 100644 --- a/moto/server.py +++ b/moto/server.py @@ -1,16 +1,54 @@ +import re import sys import argparse +from threading import Lock + from flask import Flask from werkzeug.routing import BaseConverter +from werkzeug.serving import run_simple from moto.backends import BACKENDS from moto.core.utils import convert_flask_to_httpretty_response -app = Flask(__name__) HTTP_METHODS = ["GET", "POST", "PUT", "DELETE", "HEAD"] +class DomainDispatcherApplication(object): + """ + Dispatch requests to different applications based on the "Host:" header + value. We'll match the host header value with the url_bases of each backend. + """ + + def __init__(self, create_app): + self.create_app = create_app + self.lock = Lock() + self.app_instances = {} + + def get_backend_for_host(self, host): + for backend in BACKENDS.itervalues(): + for url_base in backend.url_bases: + if re.match(url_base, 'http://%s' % host): + print url_base, 'http://%s' % host + return backend + + raise RuntimeError('Invalid host: "%s"' % host) + + def get_application(self, host): + host = host.split(':')[0] + with self.lock: + backend = self.get_backend_for_host(host) + app = self.app_instances.get(backend, None) + if app is None: + app = self.create_app(backend) + self.app_instances[backend] = app + return app + + def __call__(self, environ, start_response): + backend_app = self.get_application(environ['HTTP_HOST']) + return backend_app(environ, start_response) + + class RegexConverter(BaseConverter): # http://werkzeug.pocoo.org/docs/routing/#custom-converters def __init__(self, url_map, *items): @@ -18,25 +56,25 @@ class RegexConverter(BaseConverter): self.regex = items[0] -def configure_urls(service): - backend = BACKENDS[service] +def create_backend_app(backend): from werkzeug.routing import Map + + # Create the backend_app + backend_app = Flask(__name__) + backend_app.debug = True + # Reset view functions to reset the app - app.view_functions = {} - app.url_map = Map() - app.url_map.converters['regex'] = RegexConverter + backend_app.view_functions = {} + backend_app.url_map = Map() + backend_app.url_map.converters['regex'] = RegexConverter for url_path, handler in backend.flask_paths.iteritems(): - app.route(url_path, methods=HTTP_METHODS)(convert_flask_to_httpretty_response(handler)) + backend_app.route(url_path, methods=HTTP_METHODS)(convert_flask_to_httpretty_response(handler)) + + return backend_app def main(argv=sys.argv[1:]): - available_services = BACKENDS.keys() - parser = argparse.ArgumentParser() - parser.add_argument( - 'service', type=str, - choices=available_services, - help='Choose which mechanism you want to run') parser.add_argument( '-H', '--host', type=str, help='Which host to bind', @@ -48,10 +86,11 @@ def main(argv=sys.argv[1:]): args = parser.parse_args(argv) - configure_urls(args.service) + # Wrap the main application + main_app = DomainDispatcherApplication(create_backend_app) + main_app.debug = True - app.testing = True - app.run(host=args.host, port=args.port) + run_simple(args.host, args.port, main_app) if __name__ == '__main__': main() From ffcbaf366ea7ee09ac19709e0168985424ad4b55 Mon Sep 17 00:00:00 2001 From: Andres Riancho Date: Fri, 27 Dec 2013 18:07:43 -0300 Subject: [PATCH 27/35] Remove debug print --- moto/server.py | 1 - 1 file changed, 1 deletion(-) diff --git a/moto/server.py b/moto/server.py index d35cbae98..6209d12c3 100644 --- a/moto/server.py +++ b/moto/server.py @@ -29,7 +29,6 @@ class DomainDispatcherApplication(object): for backend in BACKENDS.itervalues(): for url_base in backend.url_bases: if re.match(url_base, 'http://%s' % host): - print url_base, 'http://%s' % host return backend raise RuntimeError('Invalid host: "%s"' % host) From 8b278eb05d8c72ab11d48bc3bddf65b3b7b08279 Mon Sep 17 00:00:00 2001 From: Steve Pulec Date: Sat, 28 Dec 2013 20:15:37 -0500 Subject: [PATCH 28/35] Clean up querystring logic. --- moto/core/responses.py | 2 +- moto/server.py | 15 ++++++++++++--- moto/sqs/urls.py | 2 +- tests/test_autoscaling/test_server.py | 5 +++-- tests/test_core/test_server.py | 16 ++++++++++------ tests/test_dynamodb/test_server.py | 5 +++-- tests/test_ec2/test_server.py | 5 +++-- tests/test_elb/test_server.py | 5 +++-- tests/test_emr/test_server.py | 5 +++-- tests/test_s3/test_server.py | 13 +++++++++---- .../test_bucket_path_server.py | 13 +++++++++---- tests/test_ses/test_server.py | 5 +++-- tests/test_sqs/test_server.py | 5 +++-- tests/test_sts/test_server.py | 5 +++-- 14 files changed, 66 insertions(+), 35 deletions(-) diff --git a/moto/core/responses.py b/moto/core/responses.py index a39795577..4a0515c4b 100644 --- a/moto/core/responses.py +++ b/moto/core/responses.py @@ -26,7 +26,7 @@ class BaseResponse(object): for key, value in request.form.iteritems(): querystring[key] = [value, ] - if querystring is None: + if not querystring: querystring.update(parse_qs(urlparse(full_url).query)) if not querystring: querystring.update(parse_qs(self.body)) diff --git a/moto/server.py b/moto/server.py index 6209d12c3..a063bbf77 100644 --- a/moto/server.py +++ b/moto/server.py @@ -20,12 +20,16 @@ class DomainDispatcherApplication(object): value. We'll match the host header value with the url_bases of each backend. """ - def __init__(self, create_app): + def __init__(self, create_app, service=None): self.create_app = create_app self.lock = Lock() self.app_instances = {} + self.service = service def get_backend_for_host(self, host): + if self.service: + return BACKENDS[self.service] + for backend in BACKENDS.itervalues(): for url_base in backend.url_bases: if re.match(url_base, 'http://%s' % host): @@ -55,7 +59,7 @@ class RegexConverter(BaseConverter): self.regex = items[0] -def create_backend_app(backend): +def create_backend_app(service): from werkzeug.routing import Map # Create the backend_app @@ -66,6 +70,8 @@ def create_backend_app(backend): backend_app.view_functions = {} backend_app.url_map = Map() backend_app.url_map.converters['regex'] = RegexConverter + + backend = BACKENDS[service] for url_path, handler in backend.flask_paths.iteritems(): backend_app.route(url_path, methods=HTTP_METHODS)(convert_flask_to_httpretty_response(handler)) @@ -74,6 +80,9 @@ def create_backend_app(backend): def main(argv=sys.argv[1:]): parser = argparse.ArgumentParser() + + # Keep this for backwards compat + parser.add_argument("service", type=str) parser.add_argument( '-H', '--host', type=str, help='Which host to bind', @@ -86,7 +95,7 @@ def main(argv=sys.argv[1:]): args = parser.parse_args(argv) # Wrap the main application - main_app = DomainDispatcherApplication(create_backend_app) + main_app = DomainDispatcherApplication(create_backend_app, service=args.service) main_app.debug = True run_simple(args.host, args.port, main_app) diff --git a/moto/sqs/urls.py b/moto/sqs/urls.py index 7e3614866..bbc6a2733 100644 --- a/moto/sqs/urls.py +++ b/moto/sqs/urls.py @@ -1,7 +1,7 @@ from .responses import QueueResponse, QueuesResponse url_bases = [ - "https?://queue(.*).amazonaws.com" + "https?://(.*?)(queue|sqs)(.*?).amazonaws.com" ] url_paths = { diff --git a/tests/test_autoscaling/test_server.py b/tests/test_autoscaling/test_server.py index d3ca05cd5..72a38750d 100644 --- a/tests/test_autoscaling/test_server.py +++ b/tests/test_autoscaling/test_server.py @@ -5,11 +5,12 @@ import moto.server as server ''' Test the different server responses ''' -server.configure_urls("autoscaling") def test_describe_autoscaling_groups(): - test_client = server.app.test_client() + backend = server.create_backend_app("autoscaling") + test_client = backend.test_client() + res = test_client.get('/?Action=DescribeLaunchConfigurations') res.data.should.contain('(.*)", res.data) diff --git a/tests/test_elb/test_server.py b/tests/test_elb/test_server.py index 9fc172dd6..c8f13e8d0 100644 --- a/tests/test_elb/test_server.py +++ b/tests/test_elb/test_server.py @@ -5,11 +5,12 @@ import moto.server as server ''' Test the different server responses ''' -server.configure_urls("elb") def test_elb_describe_instances(): - test_client = server.app.test_client() + backend = server.create_backend_app("elb") + test_client = backend.test_client() + res = test_client.get('/?Action=DescribeLoadBalancers') res.data.should.contain('DescribeLoadBalancersResponse') diff --git a/tests/test_emr/test_server.py b/tests/test_emr/test_server.py index 85ba7c4db..07aed3bc5 100644 --- a/tests/test_emr/test_server.py +++ b/tests/test_emr/test_server.py @@ -5,11 +5,12 @@ import moto.server as server ''' Test the different server responses ''' -server.configure_urls("emr") def test_describe_jobflows(): - test_client = server.app.test_client() + backend = server.create_backend_app("emr") + test_client = backend.test_client() + res = test_client.get('/?Action=DescribeJobFlows') res.data.should.contain('') diff --git a/tests/test_s3/test_server.py b/tests/test_s3/test_server.py index d2f38cb07..ee1429819 100644 --- a/tests/test_s3/test_server.py +++ b/tests/test_s3/test_server.py @@ -5,18 +5,21 @@ import moto.server as server ''' Test the different server responses ''' -server.configure_urls("s3") def test_s3_server_get(): - test_client = server.app.test_client() + backend = server.create_backend_app("s3") + test_client = backend.test_client() + res = test_client.get('/') res.data.should.contain('ListAllMyBucketsResult') def test_s3_server_bucket_create(): - test_client = server.app.test_client() + backend = server.create_backend_app("s3") + test_client = backend.test_client() + res = test_client.put('/', 'http://foobar.localhost:5000/') res.status_code.should.equal(200) @@ -36,7 +39,9 @@ def test_s3_server_bucket_create(): def test_s3_server_post_to_bucket(): - test_client = server.app.test_client() + backend = server.create_backend_app("s3") + test_client = backend.test_client() + res = test_client.put('/', 'http://foobar.localhost:5000/') res.status_code.should.equal(200) diff --git a/tests/test_s3bucket_path/test_bucket_path_server.py b/tests/test_s3bucket_path/test_bucket_path_server.py index 943615767..b8eea77e1 100644 --- a/tests/test_s3bucket_path/test_bucket_path_server.py +++ b/tests/test_s3bucket_path/test_bucket_path_server.py @@ -5,18 +5,21 @@ import moto.server as server ''' Test the different server responses ''' -server.configure_urls("s3bucket_path") def test_s3_server_get(): - test_client = server.app.test_client() + backend = server.create_backend_app("s3bucket_path") + test_client = backend.test_client() + res = test_client.get('/') res.data.should.contain('ListAllMyBucketsResult') def test_s3_server_bucket_create(): - test_client = server.app.test_client() + backend = server.create_backend_app("s3bucket_path") + test_client = backend.test_client() + res = test_client.put('/foobar', 'http://localhost:5000') res.status_code.should.equal(200) @@ -36,7 +39,9 @@ def test_s3_server_bucket_create(): def test_s3_server_post_to_bucket(): - test_client = server.app.test_client() + backend = server.create_backend_app("s3bucket_path") + test_client = backend.test_client() + res = test_client.put('/foobar', 'http://localhost:5000/') res.status_code.should.equal(200) diff --git a/tests/test_ses/test_server.py b/tests/test_ses/test_server.py index 876fa1240..06a71d137 100644 --- a/tests/test_ses/test_server.py +++ b/tests/test_ses/test_server.py @@ -5,10 +5,11 @@ import moto.server as server ''' Test the different server responses ''' -server.configure_urls("ses") def test_ses_list_identities(): - test_client = server.app.test_client() + backend = server.create_backend_app("ses") + test_client = backend.test_client() + res = test_client.get('/?Action=ListIdentities') res.data.should.contain("ListIdentitiesResponse") diff --git a/tests/test_sqs/test_server.py b/tests/test_sqs/test_server.py index 8934dcecc..4e5102edf 100644 --- a/tests/test_sqs/test_server.py +++ b/tests/test_sqs/test_server.py @@ -6,11 +6,12 @@ import moto.server as server ''' Test the different server responses ''' -server.configure_urls("sqs") def test_sqs_list_identities(): - test_client = server.app.test_client() + backend = server.create_backend_app("sqs") + test_client = backend.test_client() + res = test_client.get('/?Action=ListQueues') res.data.should.contain("ListQueuesResponse") diff --git a/tests/test_sts/test_server.py b/tests/test_sts/test_server.py index 9a505422f..a1f428caf 100644 --- a/tests/test_sts/test_server.py +++ b/tests/test_sts/test_server.py @@ -5,11 +5,12 @@ import moto.server as server ''' Test the different server responses ''' -server.configure_urls("sts") def test_sts_get_session_token(): - test_client = server.app.test_client() + backend = server.create_backend_app("sts") + test_client = backend.test_client() + res = test_client.get('/?Action=GetSessionToken') res.status_code.should.equal(200) res.data.should.contain("SessionToken") From d39d706e5cd31d35e3cecbdc5a855f6aa55a0509 Mon Sep 17 00:00:00 2001 From: Steve Pulec Date: Sun, 29 Dec 2013 21:51:43 -0500 Subject: [PATCH 29/35] Add @andresriancho to authors --- AUTHORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.md b/AUTHORS.md index 1a21fcc35..e7283eceb 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -13,3 +13,4 @@ Moto is written by Steve Pulec with contributions from: * [attili](https://github.com/attili) * [JJ Zeng](https://github.com/jjofseattle) * [Jon Haddad](https://github.com/rustyrazorblade) +* [Andres Riancho](https://github.com/andresriancho) From 235862259759012d793f475d272a4aaa8bc9c8e6 Mon Sep 17 00:00:00 2001 From: Steve Pulec Date: Mon, 30 Dec 2013 12:25:46 -0500 Subject: [PATCH 30/35] 0.2.13 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 9fa9c2302..b7dd825b0 100644 --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ if sys.version_info < (2, 7): setup( name='moto', - version='0.2.12', + version='0.2.13', description='A library that allows your python tests to easily' ' mock out the boto library', author='Steve Pulec', From c4c342c570c0b62b2e8dc61ad39563a9e46bda05 Mon Sep 17 00:00:00 2001 From: Steve Pulec Date: Mon, 30 Dec 2013 12:33:31 -0500 Subject: [PATCH 31/35] Fix bug with backend_name vs backend. --- moto/server.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/moto/server.py b/moto/server.py index a063bbf77..941f1f399 100644 --- a/moto/server.py +++ b/moto/server.py @@ -28,12 +28,12 @@ class DomainDispatcherApplication(object): def get_backend_for_host(self, host): if self.service: - return BACKENDS[self.service] + return self.service - for backend in BACKENDS.itervalues(): + for backend_name, backend in BACKENDS.iteritems(): for url_base in backend.url_bases: if re.match(url_base, 'http://%s' % host): - return backend + return backend_name raise RuntimeError('Invalid host: "%s"' % host) From b9538f74dd5119d5b761dfd1835df8465e77077f Mon Sep 17 00:00:00 2001 From: Steve Pulec Date: Mon, 30 Dec 2013 12:33:45 -0500 Subject: [PATCH 32/35] 0.2.14 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b7dd825b0..f3187b3a7 100644 --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ if sys.version_info < (2, 7): setup( name='moto', - version='0.2.13', + version='0.2.14', description='A library that allows your python tests to easily' ' mock out the boto library', author='Steve Pulec', From f0c6d1124c3bab14dd36f5a0c5e01ff373321ffa Mon Sep 17 00:00:00 2001 From: Steve Pulec Date: Mon, 30 Dec 2013 13:29:57 -0500 Subject: [PATCH 33/35] Make service argument optional so that you can actually use all services --- moto/server.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/moto/server.py b/moto/server.py index 941f1f399..ca46b6b54 100644 --- a/moto/server.py +++ b/moto/server.py @@ -82,7 +82,11 @@ def main(argv=sys.argv[1:]): parser = argparse.ArgumentParser() # Keep this for backwards compat - parser.add_argument("service", type=str) + parser.add_argument( + "service", + type=str, + nargs='?', + default=None) parser.add_argument( '-H', '--host', type=str, help='Which host to bind', From 5e7936123c29f466cca5c789f475b1f17fe44379 Mon Sep 17 00:00:00 2001 From: Steve Pulec Date: Mon, 30 Dec 2013 13:32:07 -0500 Subject: [PATCH 34/35] Add comments for optional argparse positional argument --- moto/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/moto/server.py b/moto/server.py index ca46b6b54..c1ecef074 100644 --- a/moto/server.py +++ b/moto/server.py @@ -85,7 +85,7 @@ def main(argv=sys.argv[1:]): parser.add_argument( "service", type=str, - nargs='?', + nargs='?', # http://stackoverflow.com/a/4480202/731592 default=None) parser.add_argument( '-H', '--host', type=str, From 531cd42e0f255902dbb9134ca6104e06161d5be8 Mon Sep 17 00:00:00 2001 From: Steve Pulec Date: Mon, 30 Dec 2013 13:36:05 -0500 Subject: [PATCH 35/35] 0.2.15 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index f3187b3a7..999afba42 100644 --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ if sys.version_info < (2, 7): setup( name='moto', - version='0.2.14', + version='0.2.15', description='A library that allows your python tests to easily' ' mock out the boto library', author='Steve Pulec',