Skip to content

Commit 629935c

Browse files
andrewsomethingbentranterkoalalorenzo
authored
Add support for working with VPCs (#315)
* Add VPC support to Droplet methods. * Add VPC CRUD operations. * Add VPC support to Load Balancers. * Apply suggestions from code review Co-authored-by: Ben Tranter <ben@bentranter.io> Co-authored-by: Ben Tranter <ben@bentranter.io> Co-authored-by: Lorenzo Setale <koalalorenzo@users.noreply.github.com>
1 parent 105ed7c commit 629935c

19 files changed

Lines changed: 414 additions & 17 deletions

digitalocean/Droplet.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ class Droplet(BaseAPI):
3939
user_data (str): arbitrary data to pass to droplet
4040
volumes (:obj:`str`, optional): list of blockstorage volumes
4141
monitoring (bool): True if installing the DigitalOcean monitoring agent
42+
vpc_uuid (str, optional): ID of a VPC in which the Droplet will be created
4243
4344
Attributes returned by API:
4445
* id (int): droplet id
@@ -61,7 +62,7 @@ class Droplet(BaseAPI):
6162
* ip_v6_address (:obj:`str`, optional): list of ipv6 addresses assigned
6263
* end_point (str): url of api endpoint used
6364
* volume_ids (:obj:`str`, optional): list of blockstorage volumes
64-
65+
* vpc_uuid (str, optional): ID of the VPC that the Droplet is assigned to
6566
"""
6667

6768
def __init__(self, *args, **kwargs):
@@ -94,6 +95,7 @@ def __init__(self, *args, **kwargs):
9495
self.volumes = []
9596
self.tags = []
9697
self.monitoring = None
98+
self.vpc_uuid = None
9799

98100
# This will load also the values passed
99101
super(Droplet, self).__init__(*args, **kwargs)
@@ -124,6 +126,7 @@ def create_multiple(*args, **kwargs):
124126
"private_networking": bool(kwargs.get("private_networking")),
125127
"tags": kwargs.get("tags"),
126128
"monitoring": bool(kwargs.get("monitoring")),
129+
"vpc_uuid": kwargs.get("vpc_uuid"),
127130
}
128131

129132
if kwargs.get("ssh_keys"):
@@ -556,6 +559,7 @@ def create(self, *args, **kwargs):
556559
"volumes": self.volumes,
557560
"tags": self.tags,
558561
"monitoring": bool(self.monitoring),
562+
"vpc_uuid": self.vpc_uuid,
559563
}
560564

561565
if self.user_data:

digitalocean/LoadBalancer.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ class LoadBalancer(BaseAPI):
105105
exclusive with 'tag')
106106
tag (str): A string representing a DigitalOcean Droplet tag \
107107
(mutually exclusive with 'droplet_ids')
108+
vpc_uuid (str): ID of a VPC in which the Load Balancer will be created
108109
109110
Attributes returned by API:
110111
* name (str): The Load Balancer's name
@@ -125,6 +126,7 @@ class LoadBalancer(BaseAPI):
125126
* tag (str): A string representing a DigitalOcean Droplet tag
126127
* status (string): An indication the current state of the LoadBalancer
127128
* created_at (str): The date and time when the LoadBalancer was created
129+
* vpc_uuid (str): ID of a VPC which the Load Balancer is assigned to
128130
"""
129131
def __init__(self, *args, **kwargs):
130132
self.id = None
@@ -139,6 +141,7 @@ def __init__(self, *args, **kwargs):
139141
self.tag = None
140142
self.status = None
141143
self.created_at = None
144+
self.vpc_uuid = None
142145

143146
super(LoadBalancer, self).__init__(*args, **kwargs)
144147

@@ -206,12 +209,15 @@ def create(self, *args, **kwargs):
206209
exclusive with 'tag')
207210
tag (str): A string representing a DigitalOcean Droplet tag
208211
(mutually exclusive with 'droplet_ids')
212+
vpc_uuid (str): ID of a Load Balancer in which the Droplet will be
213+
created
209214
"""
210215
rules_dict = [rule.__dict__ for rule in self.forwarding_rules]
211216

212217
params = {'name': self.name, 'region': self.region,
213218
'forwarding_rules': rules_dict,
214-
'redirect_http_to_https': self.redirect_http_to_https}
219+
'redirect_http_to_https': self.redirect_http_to_https,
220+
'vpc_uuid': self.vpc_uuid}
215221

216222
if self.droplet_ids and self.tag:
217223
raise ValueError('droplet_ids and tag are mutually exclusive args')
@@ -240,6 +246,7 @@ def create(self, *args, **kwargs):
240246
self.droplet_ids = data['load_balancer']['droplet_ids']
241247
self.status = data['load_balancer']['status']
242248
self.created_at = data['load_balancer']['created_at']
249+
self.vpc_uuid = data['load_balancer']['vpc_uuid']
243250

244251
return self
245252

@@ -253,7 +260,8 @@ def save(self):
253260
'name': self.name,
254261
'region': self.region['slug'],
255262
'forwarding_rules': forwarding_rules,
256-
'redirect_http_to_https': self.redirect_http_to_https
263+
'redirect_http_to_https': self.redirect_http_to_https,
264+
'vpc_uuid': self.vpc_uuid
257265
}
258266

259267
if self.tag:

digitalocean/Manager.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from .Snapshot import Snapshot
2323
from .Tag import Tag
2424
from .Volume import Volume
25+
from .VPC import VPC
2526
from .Project import Project
2627

2728

@@ -423,5 +424,26 @@ def get_firewall(self, firewall_id):
423424
firewall_id=firewall_id,
424425
)
425426

427+
def get_vpc(self, id):
428+
"""
429+
Returns a VPC object by its ID.
430+
Args:
431+
id (str): The VPC's ID
432+
"""
433+
return VPC.get_object(api_token=self.token, vpc_id=id)
434+
435+
def get_all_vpcs(self):
436+
"""
437+
This function returns a list of VPC objects.
438+
"""
439+
data = self.get_data("vpcs")
440+
vpcs = list()
441+
for jsoned in data['vpcs']:
442+
vpc = VPC(**jsoned)
443+
vpc.token = self.token
444+
vpcs.append(vpc)
445+
446+
return vpcs
447+
426448
def __str__(self):
427449
return "<Manager>"

digitalocean/VPC.py

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
# -*- coding: utf-8 -*-
2+
from .baseapi import BaseAPI, PATCH, POST, DELETE
3+
4+
5+
class VPC(BaseAPI):
6+
"""
7+
An object representing a DigitalOcean VPC.
8+
9+
Attributes accepted at creation time:
10+
11+
Args:
12+
name (str): A name for the VPC
13+
region (str): The slug for the region where the VPC will be created
14+
description(str): A free-form text field for describing the VPC
15+
ip_range (str): The requested range of IP addresses for the VPC in \
16+
CIDR notation
17+
18+
19+
Attributes returned by API:
20+
* id (str): A unique identifier for the VPC
21+
* name (str): The name of the VPC
22+
* region (str): The slug for the region where the VPC is located
23+
* description(str): A free-form text field for describing the VPC
24+
* ip_range (str): The requested range of IP addresses for the VPC in \
25+
CIDR notation
26+
* urn (str): The uniform resource name (URN) for the VPC
27+
* created_at (str): A string that represents when the VPC was created
28+
* default (bool): A boolen representing whether or not the VPC is the \
29+
user's default VPC for the region
30+
"""
31+
def __init__(self, *args, **kwargs):
32+
self.id = ""
33+
self.name = None
34+
self.region = None
35+
self.description = None
36+
self.ip_range = None
37+
self.urn = None
38+
self.created_at = None
39+
self.default = False
40+
41+
super(VPC, self).__init__(*args, **kwargs)
42+
43+
@classmethod
44+
def get_object(cls, api_token, vpc_id):
45+
"""
46+
Class method that will return a VPC object by its ID.
47+
"""
48+
vpc = cls(token=api_token, id=vpc_id)
49+
vpc.load()
50+
return vpc
51+
52+
def load(self):
53+
"""
54+
Load the VPC object from DigitalOcean.
55+
56+
Requires self.id to be set.
57+
"""
58+
data = self.get_data("vpcs/%s" % self.id)
59+
vpc = data["vpc"]
60+
61+
for attr in vpc.keys():
62+
setattr(self, attr, vpc[attr])
63+
64+
return self
65+
66+
def create(self):
67+
"""
68+
Create the VPC
69+
"""
70+
params = {
71+
"name": self.name,
72+
"region": self.region,
73+
"description": self.description,
74+
"ip_range": self.ip_range
75+
}
76+
77+
data = self.get_data("vpcs", type=POST, params=params)
78+
79+
if data:
80+
self.id = data['vpc']['id']
81+
self.name = data['vpc']['name']
82+
self.region = data['vpc']['region']
83+
self.description = data['vpc']['description']
84+
self.ip_range = data['vpc']['ip_range']
85+
self.urn = data['vpc']['urn']
86+
self.created_at = data['vpc']['created_at']
87+
self.default = data['vpc']['default']
88+
89+
return self
90+
91+
def rename(self, new_name):
92+
"""
93+
Rename a VPC
94+
95+
Args:
96+
name (str): The new name for the VPC
97+
"""
98+
data = self.get_data("vpcs/%s" % self.id,
99+
type=PATCH,
100+
params={"name": new_name})
101+
102+
vpc = data["vpc"]
103+
104+
for attr in vpc.keys():
105+
setattr(self, attr, vpc[attr])
106+
107+
return self
108+
109+
def rename(self, new_name):
110+
"""
111+
Rename a VPC
112+
113+
Args:
114+
name (str): The new name for the VPC
115+
"""
116+
data = self.get_data("vpcs/%s" % self.id,
117+
type=PATCH,
118+
params={"name": new_name})
119+
120+
vpc = data["vpc"]
121+
122+
for attr in vpc.keys():
123+
setattr(self, attr, vpc[attr])
124+
125+
return self
126+
127+
def destroy(self):
128+
"""
129+
Delete the VPC
130+
"""
131+
return self.get_data("vpcs/%s" % self.id, type=DELETE)
132+
133+
def __str__(self):
134+
return "<VPC: %s %s>" % (self.id, self.name)

digitalocean/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,4 @@
2929
from .Snapshot import Snapshot
3030
from .Project import Project
3131
from .Firewall import Firewall, InboundRule, OutboundRule, Destinations, Sources
32+
from .VPC import VPC

digitalocean/baseapi.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
POST = 'POST'
1515
DELETE = 'DELETE'
1616
PUT = 'PUT'
17+
PATCH = 'PATCH'
1718
REQUEST_TIMEOUT_ENV_VAR = 'PYTHON_DIGITALOCEAN_REQUEST_TIMEOUT_SEC'
1819

1920

@@ -97,7 +98,9 @@ def __perform_request(self, url, type=GET, params=None):
9798
json_dumps = lambda x: json.dumps(x)
9899
lookup = {
99100
GET: (self._session.get, {'Content-type': 'application/json'}, 'params', identity),
100-
POST: (self._session.post, {'Content-type': 'application/json'}, 'data',
101+
PATCH: (requests.patch, {'Content-type': 'application/json'},
102+
'data', json_dumps),
103+
POST: (requests.post, {'Content-type': 'application/json'}, 'data',
101104
json_dumps),
102105
PUT: (self._session.put, {'Content-type': 'application/json'}, 'data',
103106
json_dumps),

digitalocean/tests/data/droplets/all.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
}
6666
]
6767
},
68+
"vpc_uuid": "08187eaa-90eb-40d6-a8f0-0222b28ded72",
6869
"region": {
6970
"name": "New York 3",
7071
"slug": "nyc3",

digitalocean/tests/data/droplets/bytag.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
}
7373
]
7474
},
75+
"vpc_uuid": "08187eaa-90eb-40d6-a8f0-0222b28ded72",
7576
"region": {
7677
"name": "New York 3",
7778
"slug": "nyc3",

digitalocean/tests/data/droplets/single.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
}
6565
]
6666
},
67+
"vpc_uuid": "08187eaa-90eb-40d6-a8f0-0222b28ded72",
6768
"region": {
6869
"name": "New York 3",
6970
"slug": "nyc3",

digitalocean/tests/data/loadbalancer/all.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@
7070
3164444,
7171
3164445
7272
],
73-
"redirect_http_to_https": false
73+
"redirect_http_to_https": false,
74+
"vpc_uuid": "08187eaa-90eb-40d6-a8f0-0222b28ded72"
7475
},
7576
{
7677
"id": "4de7ac8b-495b-4884-9a69-1050c6793cd6",
@@ -142,7 +143,8 @@
142143
3164444,
143144
3164445
144145
],
145-
"redirect_http_to_https": false
146+
"redirect_http_to_https": false,
147+
"vpc_uuid": "08187eaa-90eb-40d6-a8f0-0222b28ded72"
146148
}],
147149
"links": {
148150
},

0 commit comments

Comments
 (0)