diff --git a/moto/ec2/models.py b/moto/ec2/models.py index e0907978f..3eaa1a4d1 100644 --- a/moto/ec2/models.py +++ b/moto/ec2/models.py @@ -19,6 +19,7 @@ from .utils import ( random_eip_association_id, random_eip_allocation_id, random_ip, + random_key_pair, ) @@ -161,6 +162,33 @@ class InstanceBackend(object): return [reservation for reservation in self.reservations.values()] +class KeyPairBackend(object): + + def __init__(self): + self.keypairs = defaultdict(dict) + super(KeyPairBackend, self).__init__() + + def create_key_pair(self, name): + if name in self.keypairs: + raise InvalidIdError(name) + self.keypairs[name] = keypair = random_key_pair() + keypair['name'] = name + return keypair + + def delete_key_pair(self, name): + if name in self.keypairs: + self.keypairs.pop(name) + return True + + def describe_key_pairs(self, filter_names=None): + results = [] + for name, keypair in self.keypairs.iteritems(): + if not filter_names or name in filter_names: + keypair['name'] = name + results.append(keypair) + return results + + class TagBackend(object): def __init__(self): @@ -675,7 +703,8 @@ class ElasticAddressBackend(object): class EC2Backend(BaseBackend, InstanceBackend, TagBackend, AmiBackend, RegionsAndZonesBackend, SecurityGroupBackend, EBSBackend, - VPCBackend, SubnetBackend, SpotRequestBackend, ElasticAddressBackend): + VPCBackend, SubnetBackend, SpotRequestBackend, ElasticAddressBackend, + KeyPairBackend): pass diff --git a/moto/ec2/responses/key_pairs.py b/moto/ec2/responses/key_pairs.py index fd0a35fe6..9c8686c83 100644 --- a/moto/ec2/responses/key_pairs.py +++ b/moto/ec2/responses/key_pairs.py @@ -1,15 +1,81 @@ +from jinja2 import Template from moto.core.responses import BaseResponse +from moto.ec2.models import ec2_backend +from moto.ec2.exceptions import InvalidIdError +from moto.ec2.utils import keypair_names_from_querystring, filters_from_querystring class KeyPairs(BaseResponse): + def create_key_pair(self): - raise NotImplementedError('KeyPairs.create_key_pair is not yet implemented') + try: + name = self.querystring.get('KeyName')[0] + keypair = ec2_backend.create_key_pair(name) + except InvalidIdError as exc: + template = Template(CREATE_KEY_PAIR_INVALID_NAME) + return template.render(keypair_id=exc.id), dict(status=400) + else: + template = Template(CREATE_KEY_PAIR_RESPONSE) + return template.render(**keypair) def delete_key_pair(self): - raise NotImplementedError('KeyPairs.delete_key_pair is not yet implemented') + name = self.querystring.get('KeyName')[0] + success = str(ec2_backend.delete_key_pair(name)).lower() + return Template(DELETE_KEY_PAIR_RESPONSE).render(success=success) def describe_key_pairs(self): - raise NotImplementedError('KeyPairs.describe_key_pairs is not yet implemented') + names = keypair_names_from_querystring(self.querystring) + filters = filters_from_querystring(self.querystring) + if len(filters) > 0: + raise NotImplementedError('Using filters in KeyPairs.describe_key_pairs is not yet implemented') + + try: + keypairs = ec2_backend.describe_key_pairs(names) + except InvalidIdError as exc: + template = Template(CREATE_KEY_PAIR_NOT_FOUND) + return template.render(keypair_id=exc.id), dict(status=400) + else: + template = Template(DESCRIBE_KEY_PAIRS_RESPONSE) + return template.render(keypairs=keypairs) def import_key_pair(self): raise NotImplementedError('KeyPairs.import_key_pair is not yet implemented') + + +DESCRIBE_KEY_PAIRS_RESPONSE = """ + 59dbff89-35bd-4eac-99ed-be587EXAMPLE + + {% for keypair in keypairs %} + + {{ keypair.name }} + {{ keypair.fingerprint }} + + {% endfor %} + + """ + + +CREATE_KEY_PAIR_RESPONSE = """ + {{ name }} + + {{ fingerprint }} + + {{ material }} + +""" + + +CREATE_KEY_PAIR_INVALID_NAME = """ +InvalidKeyPair.DuplicateThe keypair '{{ keypair_id }}' already exists.f4f76e81-8ca5-4e61-a6d5-a4a96EXAMPLE +""" + + +CREATE_KEY_PAIR_NOT_FOUND = """ +InvalidKeyPair.NotFoundThe keypair '{{ keypair_id }}' does not exist.f4f76e81-8ca5-4e61-a6d5-a4a96EXAMPLE +""" + + +DELETE_KEY_PAIR_RESPONSE = """ + 59dbff89-35bd-4eac-99ed-be587EXAMPLE + {{ success }} +""" diff --git a/moto/ec2/utils.py b/moto/ec2/utils.py index f138919db..dc5697838 100644 --- a/moto/ec2/utils.py +++ b/moto/ec2/utils.py @@ -116,6 +116,15 @@ def filters_from_querystring(querystring_dict): return response_values +def keypair_names_from_querystring(querystring_dict): + keypair_names = [] + for key, value in querystring_dict.iteritems(): + if 'KeyName' in key: + keypair_names.append(value[0]) + return keypair_names + + + filter_dict_attribute_mapping = { 'instance-state-name': 'state' } @@ -144,3 +153,27 @@ def filter_reservations(reservations, filter_dict): reservation.instances = new_instances result.append(reservation) return result + + +# not really random +def random_key_pair(): + return { + 'fingerprint': ('1f:51:ae:28:bf:89:e9:d8:1f:25:5d:37:2d:' + '7d:b8:ca:9f:f5:f1:6f'), + 'material': """---- BEGIN RSA PRIVATE KEY ---- +MIICiTCCAfICCQD6m7oRw0uXOjANBgkqhkiG9w0BAQUFADCBiDELMAkGA1UEBhMC +VVMxCzAJBgNVBAgTAldBMRAwDgYDVQQHEwdTZWF0dGxlMQ8wDQYDVQQKEwZBbWF6 +b24xFDASBgNVBAsTC0lBTSBDb25zb2xlMRIwEAYDVQQDEwlUZXN0Q2lsYWMxHzAd +BgkqhkiG9w0BCQEWEG5vb25lQGFtYXpvbi5jb20wHhcNMTEwNDI1MjA0NTIxWhcN +MTIwNDI0MjA0NTIxWjCBiDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAldBMRAwDgYD +VQQHEwdTZWF0dGxlMQ8wDQYDVQQKEwZBbWF6b24xFDASBgNVBAsTC0lBTSBDb25z +b2xlMRIwEAYDVQQDEwlUZXN0Q2lsYWMxHzAdBgkqhkiG9w0BCQEWEG5vb25lQGFt +YXpvbi5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMaK0dn+a4GmWIWJ +21uUSfwfEvySWtC2XADZ4nB+BLYgVIk60CpiwsZ3G93vUEIO3IyNoH/f0wYK8m9T +rDHudUZg3qX4waLG5M43q7Wgc/MbQITxOUSQv7c7ugFFDzQGBzZswY6786m86gpE +Ibb3OhjZnzcvQAaRHhdlQWIMm2nrAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAtCu4 +nUhVVxYUntneD9+h8Mg9q6q+auNKyExzyLwaxlAoo7TJHidbtS4J5iNmZgXL0Fkb +FFBjvSfpJIlJ00zbhNYS5f6GuoEDmFJl0ZxBHjJnyp378OD8uTs7fLvjx79LjSTb +NYiytVbZPQUQ5Yaxu2jXnimvw3rrszlaEXAMPLE +-----END RSA PRIVATE KEY-----""" + } diff --git a/tests/test_ec2/test_key_pairs.py b/tests/test_ec2/test_key_pairs.py index 7a961051e..e43718ade 100644 --- a/tests/test_ec2/test_key_pairs.py +++ b/tests/test_ec2/test_key_pairs.py @@ -1,9 +1,65 @@ import boto import sure # noqa +from boto.exception import EC2ResponseError from moto import mock_ec2 @mock_ec2 -def test_key_pairs(): - pass +def test_key_pairs_empty(): + conn = boto.connect_ec2('the_key', 'the_secret') + assert len(conn.get_all_key_pairs()) == 0 + + +@mock_ec2 +def test_key_pairs_create(): + conn = boto.connect_ec2('the_key', 'the_secret') + kp = conn.create_key_pair('foo') + assert kp.material.startswith('---- BEGIN RSA PRIVATE KEY ----') + kps = conn.get_all_key_pairs() + assert len(kps) == 1 + assert kps[0].name == 'foo' + + +@mock_ec2 +def test_key_pairs_create_two(): + conn = boto.connect_ec2('the_key', 'the_secret') + kp = conn.create_key_pair('foo') + kp = conn.create_key_pair('bar') + assert kp.material.startswith('---- BEGIN RSA PRIVATE KEY ----') + kps = conn.get_all_key_pairs() + assert len(kps) == 2 + assert kps[0].name == 'foo' + assert kps[1].name == 'bar' + kps = conn.get_all_key_pairs('foo') + assert len(kps) == 1 + assert kps[0].name == 'foo' + + +@mock_ec2 +def test_key_pairs_create_exist(): + conn = boto.connect_ec2('the_key', 'the_secret') + kp = conn.create_key_pair('foo') + assert kp.material.startswith('---- BEGIN RSA PRIVATE KEY ----') + assert len(conn.get_all_key_pairs()) == 1 + conn.create_key_pair.when.called_with('foo').should.throw( + EC2ResponseError, + "The keypair 'foo' already exists." + ) + + +@mock_ec2 +def test_key_pairs_delete_no_exist(): + conn = boto.connect_ec2('the_key', 'the_secret') + assert len(conn.get_all_key_pairs()) == 0 + r = conn.delete_key_pair('foo') + r.should.be.ok + + +@mock_ec2 +def test_key_pairs_delete_exist(): + conn = boto.connect_ec2('the_key', 'the_secret') + conn.create_key_pair('foo') + r = conn.delete_key_pair('foo') + r.should.be.ok + assert len(conn.get_all_key_pairs()) == 0