From a088a8b5748e60e224aa138d17da58da8ba72269 Mon Sep 17 00:00:00 2001 From: Joro Date: Thu, 9 Jun 2016 13:46:01 -0700 Subject: [PATCH 1/6] Initial support for creating multiple droplets with one API call - no ssh_keys option. --- digitalocean/Droplet.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/digitalocean/Droplet.py b/digitalocean/Droplet.py index 4ab36df2..c4a84efb 100644 --- a/digitalocean/Droplet.py +++ b/digitalocean/Droplet.py @@ -102,6 +102,36 @@ def get_object(cls, api_token, droplet_id): droplet.load() return droplet + @classmethod + def create_multiple(**kwargs): + api = BaseAPI(token=kwargs.get("token")) + + data = { + "names": kwargs.get("names"), + "size": kwargs.get("size_slug") or kwargs.get("size"), + "image": kwargs.get("image"), + "region": kwargs.get("region"), + "backups": bool(kwargs.get("backups")), + "ipv6": bool(kwargs.get("ipv6")), + "private_networking": bool(kwargs.get("private_networking")), + } + + if kwargs.get("user_data"): + data["user_data"] = kwargs["user_data"] + + droplets = [] + + data = api.get_data("droplets", type=POST, params=data) + + if data: + action_ids = [data["links"]["actions"][0]["id"]] + for droplet_json in data["droplets"]: + droplet = Droplet(droplet_json) + droplet.action_ids = action_ids + droplets.append(droplet) + + return droplets + def __check_actions_in_data(self, data): # reloading actions if actions is provided. if u"actions" in data: From 6f767f814cae94056ec761643a247f5186f62cca Mon Sep 17 00:00:00 2001 From: Joro Date: Thu, 9 Jun 2016 19:50:44 -0700 Subject: [PATCH 2/6] Fixes after testing. --- digitalocean/Droplet.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/digitalocean/Droplet.py b/digitalocean/Droplet.py index c4a84efb..f7c42a08 100644 --- a/digitalocean/Droplet.py +++ b/digitalocean/Droplet.py @@ -103,7 +103,8 @@ def get_object(cls, api_token, droplet_id): return droplet @classmethod - def create_multiple(**kwargs): + def create_multiple(*args, **kwargs): + print(kwargs) api = BaseAPI(token=kwargs.get("token")) data = { @@ -126,7 +127,8 @@ def create_multiple(**kwargs): if data: action_ids = [data["links"]["actions"][0]["id"]] for droplet_json in data["droplets"]: - droplet = Droplet(droplet_json) + droplet_json['token'] = kwargs["token"] + droplet = Droplet(**droplet_json) droplet.action_ids = action_ids droplets.append(droplet) From 2d4a798462b5582aa92e5aa676c7971a97494e37 Mon Sep 17 00:00:00 2001 From: Joro Date: Fri, 10 Jun 2016 12:59:06 -0700 Subject: [PATCH 3/6] Removed some debug output --- digitalocean/Droplet.py | 1 - 1 file changed, 1 deletion(-) diff --git a/digitalocean/Droplet.py b/digitalocean/Droplet.py index f7c42a08..04d39e98 100644 --- a/digitalocean/Droplet.py +++ b/digitalocean/Droplet.py @@ -104,7 +104,6 @@ def get_object(cls, api_token, droplet_id): @classmethod def create_multiple(*args, **kwargs): - print(kwargs) api = BaseAPI(token=kwargs.get("token")) data = { From 770b6d658f2bf4f740bc56a57ed1596ade57efcb Mon Sep 17 00:00:00 2001 From: Joro Date: Thu, 16 Jun 2016 22:25:25 -0700 Subject: [PATCH 4/6] Add support for using ssh_keys. --- digitalocean/Droplet.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/digitalocean/Droplet.py b/digitalocean/Droplet.py index 04d39e98..2d10ac5a 100644 --- a/digitalocean/Droplet.py +++ b/digitalocean/Droplet.py @@ -116,6 +116,11 @@ def create_multiple(*args, **kwargs): "private_networking": bool(kwargs.get("private_networking")), } + if kwargs.get("ssh_keys"): + data["ssh_keys"] = Droplet.__get_ssh_keys_id_or_fingerprint( + kwargs["ssh_keys"], kwargs.get("token"), + kwargs["names"][0]) + if kwargs.get("user_data"): data["user_data"] = kwargs["user_data"] @@ -126,7 +131,7 @@ def create_multiple(*args, **kwargs): if data: action_ids = [data["links"]["actions"][0]["id"]] for droplet_json in data["droplets"]: - droplet_json['token'] = kwargs["token"] + droplet_json["token"] = kwargs["token"] droplet = Droplet(**droplet_json) droplet.action_ids = action_ids droplets.append(droplet) @@ -460,14 +465,15 @@ def change_kernel(self, kernel, return_dict=True): return_dict ) - def __get_ssh_keys_id_or_fingerprint(self): + @classmethod + def __get_ssh_keys_id_or_fingerprint(ssh_keys, token, name): """ Check and return a list of SSH key IDs or fingerprints according to DigitalOcean's API. This method is used to check and create a droplet with the correct SSH keys. """ ssh_keys_id = list() - for ssh_key in self.ssh_keys: + for ssh_key in ssh_keys: if type(ssh_key) in [int, type(2 ** 64)]: ssh_keys_id.append(int(ssh_key)) @@ -488,12 +494,12 @@ def __get_ssh_keys_id_or_fingerprint(self): else: key = SSHKey() - key.token = self.token + key.token = token results = key.load_by_pub_key(ssh_key) if results is None: key.public_key = ssh_key - key.name = "SSH Key %s" % self.name + key.name = "SSH Key %s" % name key.create() else: key = results @@ -521,12 +527,16 @@ def create(self, *args, **kwargs): if not self.size_slug and self.size: self.size_slug = self.size + ssh_keys_id = Droplet.__get_ssh_keys_id_or_fingerprint(self.ssh_keys, + self.token, + self.name) + data = { "name": self.name, "size": self.size_slug, "image": self.image, "region": self.region, - "ssh_keys": self.__get_ssh_keys_id_or_fingerprint(), + "ssh_keys": ssh_keys_id, "backups": bool(self.backups), "ipv6": bool(self.ipv6), "private_networking": bool(self.private_networking), From 3441d985ff452391a75930d9f9de923080993de3 Mon Sep 17 00:00:00 2001 From: Joro Date: Fri, 17 Jun 2016 11:36:51 -0700 Subject: [PATCH 5/6] Use static instead of a class method. --- digitalocean/Droplet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/digitalocean/Droplet.py b/digitalocean/Droplet.py index 2d10ac5a..e7e81b66 100644 --- a/digitalocean/Droplet.py +++ b/digitalocean/Droplet.py @@ -465,7 +465,7 @@ def change_kernel(self, kernel, return_dict=True): return_dict ) - @classmethod + @staticmethod def __get_ssh_keys_id_or_fingerprint(ssh_keys, token, name): """ Check and return a list of SSH key IDs or fingerprints according From d5e651d84695efa6c00d3fbd80ea0cc508e68acb Mon Sep 17 00:00:00 2001 From: Joro Date: Thu, 14 Jul 2016 15:38:30 -0700 Subject: [PATCH 6/6] Add a unit test for create_multiple. --- .../data/droplet_actions/create_multiple.json | 76 +++++++++++++++++++ digitalocean/tests/test_droplet.py | 37 +++++++++ 2 files changed, 113 insertions(+) create mode 100644 digitalocean/tests/data/droplet_actions/create_multiple.json diff --git a/digitalocean/tests/data/droplet_actions/create_multiple.json b/digitalocean/tests/data/droplet_actions/create_multiple.json new file mode 100644 index 00000000..161803af --- /dev/null +++ b/digitalocean/tests/data/droplet_actions/create_multiple.json @@ -0,0 +1,76 @@ +{ + "droplets": [{ + "id": 3164494, + "name": "example.com", + "memory": 512, + "vcpus": 1, + "disk": 20, + "locked": true, + "status": "new", + "kernel": { + "id": 2233, + "name": "Ubuntu 14.04 x64 vmlinuz-3.13.0-37-generic", + "version": "3.13.0-37-generic" + }, + "created_at": "2014-11-14T16:36:31Z", + "features": [ + "virtio", + "backups", + "ipv6" + ], + "backup_ids": [ + + ], + "snapshot_ids": [ + + ], + "image": { + }, + "size_slug": "512mb", + "networks": { + }, + "region": { + } + }, { + "id": 3164495, + "name": "example2.com", + "memory": 512, + "vcpus": 1, + "disk": 20, + "locked": true, + "status": "new", + "kernel": { + "id": 2233, + "name": "Ubuntu 14.04 x64 vmlinuz-3.13.0-37-generic", + "version": "3.13.0-37-generic" + }, + "created_at": "2014-11-14T16:36:31Z", + "features": [ + "virtio", + "backups", + "ipv6" + ], + "backup_ids": [ + + ], + "snapshot_ids": [ + + ], + "image": { + }, + "size_slug": "512mb", + "networks": { + }, + "region": { + } + }], + "links": { + "actions": [ + { + "id": 36805096, + "rel": "create", + "href": "https://api.digitalocean.com/v2/actions/36805096" + } + ] + } +} \ No newline at end of file diff --git a/digitalocean/tests/test_droplet.py b/digitalocean/tests/test_droplet.py index 64a34023..e14b0d6a 100644 --- a/digitalocean/tests/test_droplet.py +++ b/digitalocean/tests/test_droplet.py @@ -812,6 +812,43 @@ def test_create_no_keys(self): self.assertEqual(droplet.id, 3164494) self.assertEqual(droplet.action_ids, [36805096]) + @responses.activate + def test_create_multiple_no_keys(self): + data = self.load_from_file('droplet_actions/create_multiple.json') + + responses.add(responses.POST, self.base_url + "droplets", + body=data, + status=202, + content_type='application/json') + + + droplets = digitalocean.Droplet.create_multiple(names=["example.com", + "example2.com"], + size_slug="512mb", + image="ubuntu-14-04-x64", + region="nyc3", + backups=True, + ipv6=True, + private_networking=True, + user_data="Some user data.", + token=self.token) + self.assert_url_query_equal(responses.calls[0].request.url, + self.base_url + "droplets") + self.assertEqual(len(droplets), 2) + self.assertEqual(droplets[0].id, 3164494) + self.assertEqual(droplets[1].id, 3164495) + self.assertEqual(droplets[0].action_ids, [36805096]) + self.assertEqual(droplets[1].action_ids, [36805096]) + + self.maxDiff = None + self.assertEqual( + json.loads(responses.calls[0].request.body), + {u"names": [u"example.com", u"example2.com"], u"region": u"nyc3", + u"user_data": u"Some user data.", u"ipv6": True, + u"private_networking": True, u"backups": True, + u"image": u"ubuntu-14-04-x64", u"size": u"512mb"}) + + @responses.activate def test_get_actions(self): data = self.load_from_file('actions/multi.json')