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.Duplicate
The keypair '{{ keypair_id }}' already exists.f4f76e81-8ca5-4e61-a6d5-a4a96EXAMPLE
+"""
+
+
+CREATE_KEY_PAIR_NOT_FOUND = """
+InvalidKeyPair.NotFound
The 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