/
ScwPy.py
237 lines (201 loc) · 8.15 KB
/
ScwPy.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
# Management for Scaleway servers
from scaleway.apis import ComputeAPI
version = "0.1.0"
# TODO: Separate classes into their own files?
# TODO: make all classes consistent in how they obtain info.
# e.g. Server.get_ip(), using IDs instead of matching up addresses. declare Server.ip_ID etc. in __init__.
# This is required for Volumes as the only reliable identifiers are IDs.
class Manager:
"""
Root of all evil. Instances of Server, IP, etc. should all be created and referenced using commands here.
"""
def __init__(self, auth_id, organization_id, region):
self.api = ComputeAPI(region=region, auth_token=auth_id)
self.org_id = organization_id
self.servers = set()
self.ips = set()
self.volumes = set()
self.build_ip_list()
self.build_server_list()
self.build_volume_list()
def build_server_list(self):
"""
Repopulate the set of Server instances. Will destroy any existing set and all references to it,
reloading information from the Scaleway API. Accessible from Manager.servers
"""
self.servers = set()
servers_dict = self.api.query().servers.get()["servers"]
for server in servers_dict:
self.servers.add(Server(self, server))
def build_ip_list(self):
"""
Repopulate the set of IP instances. Will destroy any existing set and all references to it,
reloading information from the Scaleway API. Accessible from Manager.ips
"""
self.ips = set()
ips_dict = self.api.query().ips().get()
for ip in ips_dict['ips']:
self.ips.add(IP(self, ip))
def build_volume_list(self):
"""
Repopulate the set of Volume instances. Will destroy any existing set and all reference to it,
reloading information from the Scaleway API. Accessible from Manager.volumes
"""
self.volumes = set()
volumes_dict = self.api.query().volumes().get()
for volume in volumes_dict["volumes"]:
self.volumes.add(Volume(self, volume))
def get_server_by_name(self, name: str):
for server in self.servers:
if server.name == name:
return server
raise LookupError("No server with that name found!")
def get_server_by_id(self, id_str: str):
for server in self.servers:
if server.id == id_str:
return server
raise LookupError("No server with that ID found!")
def get_ip_by_id(self, id_str: str):
"""
Obtain an IP from a known ID string.
"""
for ip in self.ips:
if id_str == ip.id:
return ip
raise LookupError("No IP with that ID found!")
def get_ip_by_address(self, address: str):
"""
Obtain an IP from a known public address.
"""
for ip in self.ips:
if address == ip.address:
return ip
raise LookupError("No IP with that address found!")
def new_ip(self):
"""Creates a brand new IP through the Scaleway API and creates an associated IP instance.
Returns that instance."""
result = self.api.query().ips.post({"organization": self.org_id})['ip']
print(result)
new_ip = IP(self, result)
self.ips.add(new_ip)
return new_ip
class IP:
"""A class to hold information and functions pertaining to a Scaleway IP object."""
def __init__(self, manager, dict_info):
self.manager = manager
self.address = dict_info["address"]
self.id = dict_info["id"]
# Check if `server` is set to None, if not then pull out the id of the server we're attached to:
if dict_info["server"] is None:
self.server_id = None
else:
self.server_id = dict_info['server']['id']
def get_server(self):
"""
Returns an instance of Server for the server associated with this IP, if attached to one.
Otherwise returns None
:rtype: 'Server'
"""
if self.server is None: # TODO: Rewrite this to use IDs or something?
return Empty()
for ip in self.manager.ips:
if ip.address == self.address:
return ip
return None # line should only be reached if above 'if' never came True.
def delete(self):
"""
Deletes the IP from your Scaleway account and removes this instance from the Manager.ips list.
Cannot be undone.
"""
print("Deleted", self.address)
result = self.manager.api.query().ips(self.id).delete()
self.manager.ips.remove(self)
# TODO: Figure out something more useful to return?
return result
def __str__(self):
return f"{self.address}"
class Server:
"""
A class to hold information and functions pertaining to a Scaleway Server instance
"""
def __init__(self, manager, dict_info):
self.manager = manager
self.name = dict_info["name"]
self.id = dict_info["id"]
self.state = dict_info["state"]
self.state_detail = dict_info["state_detail"]
# Check if `public_ip` is set to None, if not then pull out our address:
if dict_info["public_ip"] is None:
self.address = None
else:
self.address = dict_info["public_ip"]["address"]
self.ip_id = dict_info["public_ip"]["id"]
self.private_ip = dict_info["private_ip"]
def get_ip(self):
"""Return an instance of IP for the address that's associated with this server, if one is attached.
Otherwise returns None.
:rtype: 'IP'
"""
if self.address is None:
return Empty()
else:
for ip in self.manager.ips:
if ip.address == self.address:
return ip
return Empty() # line should not be reached
def volumes(self):
"""Returns a set of instances of Volume for all the drives attached to this server."""
raise NotImplemented("I haven't gotten around to this yet :(")
def power_on(self):
"""Put the plug back in. Does not update state. Use Server.check_state for that"""
result = self.manager.api.query().servers(self.id).action.post({"action": "poweron"})
return result
def power_off(self):
"""Pull the plug on a server."""
result = self.manager.api.query().servers(self.id).action.post({"action": "poweroff"})
return result
def attach_ip(self, ip: 'IP'):
"""Attaches the given IP instances to this server"""
result = self.manager.api.query().ips(ip.id).patch({"server": self.id})
return result
def detach_ip(self):
"""Remove the IP attached to the server."""
result = self.manager.api.query().ips(self.ip().id).patch({"server": None})
def update_state(self):
"""TODO: Update the state and other parameters of this instance from the API without recreating the instance"""
pass
class Volume:
"""
A class to hold information and functions pertaining to a Scaleway Volume.
"""
def __init__(self, manager, volume_dict):
# TODO: more... stuff?
self.manager = manager
self.name = volume_dict["name"]
self.id = volume_dict["id"]
self.volume_type = volume_dict["volume_type"]
if volume_dict["server"] is not None:
self.server_id = volume_dict["server"]["id"]
else:
self.server_id = None
def get_server(self): # This'll have to be done using IDs, same should be applied to other object types
if self.server_id is None:
return Empty()
else:
for server in self.manager.servers:
if server.id == self.server_id:
return server
return Empty() # line should not be reached
class Empty:
"""
Class to be returned when nothing relevant is found for a call.
Returns `None` for nearly all attributes.
"""
def return_none(self, *args, **kwargs):
return None
def __getattr__(self, item):
return self.return_none()
def __repr__(self):
return None
def __str__(self):
return None