twc-cli 1.0.0rc0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of twc-cli might be problematic. Click here for more details.
- CHANGELOG.md +5 -0
- COPYING +22 -0
- twc/__init__.py +5 -0
- twc/__main__.py +21 -0
- twc/__version__.py +13 -0
- twc/api/__init__.py +2 -0
- twc/api/client.py +591 -0
- twc/api/exceptions.py +26 -0
- twc/commands/__init__.py +286 -0
- twc/commands/account.py +178 -0
- twc/commands/config.py +46 -0
- twc/commands/server.py +2148 -0
- twc/commands/ssh_key.py +296 -0
- twc/fmt.py +188 -0
- twc_cli-1.0.0rc0.dist-info/COPYING +22 -0
- twc_cli-1.0.0rc0.dist-info/METADATA +80 -0
- twc_cli-1.0.0rc0.dist-info/RECORD +19 -0
- twc_cli-1.0.0rc0.dist-info/WHEEL +4 -0
- twc_cli-1.0.0rc0.dist-info/entry_points.txt +3 -0
twc/commands/server.py
ADDED
|
@@ -0,0 +1,2148 @@
|
|
|
1
|
+
"""Cloud Server management commands."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
import datetime
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
from click_aliases import ClickAliasedGroup
|
|
10
|
+
|
|
11
|
+
from twc import fmt
|
|
12
|
+
from . import (
|
|
13
|
+
create_client,
|
|
14
|
+
handle_request,
|
|
15
|
+
options,
|
|
16
|
+
log,
|
|
17
|
+
confirm_action,
|
|
18
|
+
MutuallyExclusiveOption,
|
|
19
|
+
GLOBAL_OPTIONS,
|
|
20
|
+
OUTPUT_FORMAT_OPTION,
|
|
21
|
+
DEFAULT_CONFIGURATOR_ID,
|
|
22
|
+
REGIONS_WITH_CONFIGURATOR,
|
|
23
|
+
REGIONS_WITH_IPV6,
|
|
24
|
+
)
|
|
25
|
+
from .ssh_key import (
|
|
26
|
+
_ssh_key_list,
|
|
27
|
+
_ssh_key_new,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@handle_request
|
|
32
|
+
def _server_list(client, **kwargs):
|
|
33
|
+
return client.get_servers(**kwargs)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@handle_request
|
|
37
|
+
def _server_get(client, *args, **kwargs):
|
|
38
|
+
return client.get_server(*args, **kwargs)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@handle_request
|
|
42
|
+
def _server_create(client, *args, **kwargs):
|
|
43
|
+
return client.create_server(*args, **kwargs)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@handle_request
|
|
47
|
+
def _server_update(client, *args, **kwargs):
|
|
48
|
+
return client.update_server(*args, **kwargs)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@handle_request
|
|
52
|
+
def _server_action(client, *args, **kwargs):
|
|
53
|
+
return client.do_action_with_server(*args, **kwargs)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@handle_request
|
|
57
|
+
def _server_remove(client, *args):
|
|
58
|
+
return client.delete_server(*args)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@handle_request
|
|
62
|
+
def _get_server_configurators(client):
|
|
63
|
+
return client.get_server_configurators()
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@handle_request
|
|
67
|
+
def _server_presets(client):
|
|
68
|
+
return client.get_server_presets()
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@handle_request
|
|
72
|
+
def _server_software(client):
|
|
73
|
+
return client.get_server_software()
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@handle_request
|
|
77
|
+
def _server_os_images(client):
|
|
78
|
+
return client.get_server_os_images()
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@handle_request
|
|
82
|
+
def _server_logs(client, *args, **kwargs):
|
|
83
|
+
return client.get_server_logs(*args, **kwargs)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@handle_request
|
|
87
|
+
def _server_set_boot_mode(client, *args, **kwargs):
|
|
88
|
+
return client.set_server_boot_mode(*args, **kwargs)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@handle_request
|
|
92
|
+
def _server_set_nat_mode(client, *args, **kwargs):
|
|
93
|
+
return client.set_server_nat_mode(*args, **kwargs)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@handle_request
|
|
97
|
+
def _server_ip_list(client, *args):
|
|
98
|
+
return client.get_ips_by_server_id(*args)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@handle_request
|
|
102
|
+
def _server_ip_add(client, *args, **kwargs):
|
|
103
|
+
return client.add_ip_addr(*args, **kwargs)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@handle_request
|
|
107
|
+
def _server_ip_remove(client, *args, **kwargs):
|
|
108
|
+
return client.delete_ip_addr(*args, **kwargs)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@handle_request
|
|
112
|
+
def _server_ip_set_ptr(client, *args, **kwargs):
|
|
113
|
+
return client.update_ip_addr(*args, **kwargs)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@handle_request
|
|
117
|
+
def _server_disk_list(client, *args):
|
|
118
|
+
return client.get_disks_by_server_id(*args)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@handle_request
|
|
122
|
+
def _server_disk_get(client, *args):
|
|
123
|
+
return client.get_disk(*args)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@handle_request
|
|
127
|
+
def _server_disk_add(client, *args, **kwargs):
|
|
128
|
+
return client.add_disk(*args, **kwargs)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@handle_request
|
|
132
|
+
def _server_disk_remove(client, *args, **kwargs):
|
|
133
|
+
return client.delete_disk(*args, **kwargs)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@handle_request
|
|
137
|
+
def _server_disk_resize(client, *args, **kwargs):
|
|
138
|
+
return client.update_disk(*args, **kwargs)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@handle_request
|
|
142
|
+
def _server_disk_autobackup_status(client, *args, **kwargs):
|
|
143
|
+
return client.get_disk_autobackup_settings(*args, **kwargs)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@handle_request
|
|
147
|
+
def _server_disk_autobackup_update(client, *args, **kwargs):
|
|
148
|
+
return client.update_disk_autobackup_settings(*args, **kwargs)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
@handle_request
|
|
152
|
+
def _server_backup_list(client, *args, **kwargs):
|
|
153
|
+
return client.get_disk_backups(*args, **kwargs)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
@handle_request
|
|
157
|
+
def _server_backup_get(client, *args, **kwargs):
|
|
158
|
+
return client.get_disk_backup(*args, **kwargs)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
@handle_request
|
|
162
|
+
def _server_backup_create(client, *args, **kwargs):
|
|
163
|
+
return client.create_disk_backup(*args, **kwargs)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
@handle_request
|
|
167
|
+
def _server_backup_set_property(client, *args, **kwargs):
|
|
168
|
+
return client.update_disk_backup(*args, **kwargs)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
@handle_request
|
|
172
|
+
def _server_backup_remove(client, *args, **kwargs):
|
|
173
|
+
return client.delete_disk_backup(*args, **kwargs)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
@handle_request
|
|
177
|
+
def _server_backup_do_action(client, *args, **kwargs):
|
|
178
|
+
return client.do_action_with_disk_backup(*args, **kwargs)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
# ------------------------------------------------------------- #
|
|
182
|
+
# $ twc server #
|
|
183
|
+
# ------------------------------------------------------------- #
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
@click.group("server", cls=ClickAliasedGroup)
|
|
187
|
+
@options(GLOBAL_OPTIONS[:2])
|
|
188
|
+
def server():
|
|
189
|
+
"""Manage Cloud Servers."""
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
# ------------------------------------------------------------- #
|
|
193
|
+
# $ twc server list #
|
|
194
|
+
# ------------------------------------------------------------- #
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def print_servers(response: object, filters: str, ids: bool):
|
|
198
|
+
if filters:
|
|
199
|
+
servers = fmt.filter_list(response.json()["servers"], filters)
|
|
200
|
+
else:
|
|
201
|
+
servers = response.json()["servers"]
|
|
202
|
+
|
|
203
|
+
if ids:
|
|
204
|
+
for _server in servers:
|
|
205
|
+
click.echo(_server["id"])
|
|
206
|
+
return
|
|
207
|
+
|
|
208
|
+
table = fmt.Table()
|
|
209
|
+
table.header(
|
|
210
|
+
[
|
|
211
|
+
"ID",
|
|
212
|
+
"NAME",
|
|
213
|
+
"REGION",
|
|
214
|
+
"STATUS",
|
|
215
|
+
"IPV4",
|
|
216
|
+
]
|
|
217
|
+
)
|
|
218
|
+
for _server in servers:
|
|
219
|
+
for network in _server["networks"]:
|
|
220
|
+
if network["type"] == "public":
|
|
221
|
+
for ip_addr in network["ips"]:
|
|
222
|
+
if (
|
|
223
|
+
ip_addr["type"] == "ipv4"
|
|
224
|
+
and ip_addr["is_main"] is True
|
|
225
|
+
):
|
|
226
|
+
main_ipv4 = ip_addr["ip"]
|
|
227
|
+
table.row(
|
|
228
|
+
[
|
|
229
|
+
_server["id"],
|
|
230
|
+
_server["name"],
|
|
231
|
+
_server["location"],
|
|
232
|
+
_server["status"],
|
|
233
|
+
main_ipv4,
|
|
234
|
+
]
|
|
235
|
+
)
|
|
236
|
+
table.print()
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
@server.command("list", aliases=["ls"], help="List Cloud Servers.")
|
|
240
|
+
@options(GLOBAL_OPTIONS)
|
|
241
|
+
@options(OUTPUT_FORMAT_OPTION)
|
|
242
|
+
@click.option("--filter", "-f", "filters", default="", help="Filter output.")
|
|
243
|
+
@click.option("--region", help="Use region (location).")
|
|
244
|
+
@click.option("--limit", default=100, help="Limit [default: 100].")
|
|
245
|
+
@click.option("--ids", is_flag=True, help="Print only server IDs.")
|
|
246
|
+
def server_list(
|
|
247
|
+
config, profile, verbose, region, output_format, filters, limit, ids
|
|
248
|
+
):
|
|
249
|
+
if filters:
|
|
250
|
+
filters = filters.replace("region", "location")
|
|
251
|
+
if region:
|
|
252
|
+
if filters:
|
|
253
|
+
filters = filters + f",location:{region}"
|
|
254
|
+
else:
|
|
255
|
+
filters = f"location:{region}"
|
|
256
|
+
|
|
257
|
+
client = create_client(config, profile)
|
|
258
|
+
response = _server_list(client, limit=limit)
|
|
259
|
+
fmt.printer(
|
|
260
|
+
response,
|
|
261
|
+
output_format=output_format,
|
|
262
|
+
filters=filters,
|
|
263
|
+
func=print_servers,
|
|
264
|
+
ids=ids,
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
# ------------------------------------------------------------- #
|
|
269
|
+
# $ twc server get #
|
|
270
|
+
# ------------------------------------------------------------- #
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def print_server(response: object):
|
|
274
|
+
_server = response.json()["server"]
|
|
275
|
+
|
|
276
|
+
for network in _server["networks"]:
|
|
277
|
+
if network["type"] == "public":
|
|
278
|
+
for ip_addr in network["ips"]:
|
|
279
|
+
if ip_addr["type"] == "ipv4" and ip_addr["is_main"] is True:
|
|
280
|
+
main_ipv4 = ip_addr["ip"]
|
|
281
|
+
|
|
282
|
+
table = fmt.Table()
|
|
283
|
+
table.header(
|
|
284
|
+
[
|
|
285
|
+
"ID",
|
|
286
|
+
"NAME",
|
|
287
|
+
"REGION",
|
|
288
|
+
"STATUS",
|
|
289
|
+
"IPV4",
|
|
290
|
+
]
|
|
291
|
+
)
|
|
292
|
+
table.row(
|
|
293
|
+
[
|
|
294
|
+
_server["id"],
|
|
295
|
+
_server["name"],
|
|
296
|
+
_server["location"],
|
|
297
|
+
_server["status"],
|
|
298
|
+
main_ipv4,
|
|
299
|
+
]
|
|
300
|
+
)
|
|
301
|
+
table.print()
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def print_server_networks(response: object):
|
|
305
|
+
networks = response.json()["server"]["networks"]
|
|
306
|
+
for network in networks:
|
|
307
|
+
if network["type"] == "public":
|
|
308
|
+
table = fmt.Table()
|
|
309
|
+
table.header(
|
|
310
|
+
[
|
|
311
|
+
"NETWORK",
|
|
312
|
+
"ADDRESS",
|
|
313
|
+
"VERSION",
|
|
314
|
+
"PTR",
|
|
315
|
+
"PRIMARY",
|
|
316
|
+
]
|
|
317
|
+
)
|
|
318
|
+
for ip_addr in network["ips"]:
|
|
319
|
+
table.row(
|
|
320
|
+
[
|
|
321
|
+
network["type"],
|
|
322
|
+
ip_addr["ip"],
|
|
323
|
+
ip_addr["type"],
|
|
324
|
+
ip_addr["ptr"],
|
|
325
|
+
ip_addr["is_main"],
|
|
326
|
+
]
|
|
327
|
+
)
|
|
328
|
+
table.print()
|
|
329
|
+
else:
|
|
330
|
+
table = fmt.Table()
|
|
331
|
+
for ip_addr in network["ips"]:
|
|
332
|
+
table.row([network["type"], ip_addr["ip"], ip_addr["type"]])
|
|
333
|
+
table.print()
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def print_server_disks(response: object):
|
|
337
|
+
disks = response.json()["server"]["disks"]
|
|
338
|
+
table = fmt.Table()
|
|
339
|
+
table.header(
|
|
340
|
+
[
|
|
341
|
+
"ID",
|
|
342
|
+
"NAME",
|
|
343
|
+
"MOUNTED",
|
|
344
|
+
"SYSTEM",
|
|
345
|
+
"TYPE",
|
|
346
|
+
"STATUS",
|
|
347
|
+
"SIZE",
|
|
348
|
+
"USED",
|
|
349
|
+
]
|
|
350
|
+
)
|
|
351
|
+
for disk in disks:
|
|
352
|
+
table.row(
|
|
353
|
+
[
|
|
354
|
+
disk["id"],
|
|
355
|
+
disk["system_name"],
|
|
356
|
+
disk["is_mounted"],
|
|
357
|
+
disk["is_system"],
|
|
358
|
+
disk["type"],
|
|
359
|
+
disk["status"],
|
|
360
|
+
str(round(disk["size"] / 1024)) + "G",
|
|
361
|
+
str(round(disk["used"] / 1024, 1)) + "G",
|
|
362
|
+
]
|
|
363
|
+
)
|
|
364
|
+
table.print()
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
@server.command("get", help="Get Cloud Server.")
|
|
368
|
+
@options(GLOBAL_OPTIONS)
|
|
369
|
+
@options(OUTPUT_FORMAT_OPTION)
|
|
370
|
+
@click.option(
|
|
371
|
+
"--status",
|
|
372
|
+
is_flag=True,
|
|
373
|
+
help="Display status and exit with 0 if status is 'on'.",
|
|
374
|
+
)
|
|
375
|
+
@click.option("--networks", is_flag=True, help="Display networks.")
|
|
376
|
+
@click.option("--disks", is_flag=True, help="Display disks.")
|
|
377
|
+
@click.argument("server_id", type=int, required=True)
|
|
378
|
+
def server_get(
|
|
379
|
+
config, profile, verbose, output_format, status, networks, disks, server_id
|
|
380
|
+
):
|
|
381
|
+
client = create_client(config, profile)
|
|
382
|
+
response = _server_get(client, server_id)
|
|
383
|
+
if status:
|
|
384
|
+
_status = response.json()["server"]["status"]
|
|
385
|
+
if _status == "on":
|
|
386
|
+
click.echo(_status)
|
|
387
|
+
sys.exit(0)
|
|
388
|
+
else:
|
|
389
|
+
sys.exit(_status)
|
|
390
|
+
if networks:
|
|
391
|
+
print_server_networks(response)
|
|
392
|
+
sys.exit(0)
|
|
393
|
+
if disks:
|
|
394
|
+
print_server_disks(response)
|
|
395
|
+
sys.exit(0)
|
|
396
|
+
fmt.printer(response, output_format=output_format, func=print_server)
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
# ------------------------------------------------------------- #
|
|
400
|
+
# $ twc server create #
|
|
401
|
+
# ------------------------------------------------------------- #
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
def get_os_name_by_id(os_images: list, os_id: int) -> str:
|
|
405
|
+
"""Return human readable operating system name by OS ID::
|
|
406
|
+
|
|
407
|
+
79 --> ubuntu-22.04
|
|
408
|
+
65 --> windows-2012-standard
|
|
409
|
+
"""
|
|
410
|
+
for os in os_images:
|
|
411
|
+
if os["id"] == os_id:
|
|
412
|
+
if os["family"] == "linux":
|
|
413
|
+
return f"{os['name']}-{os['version']}"
|
|
414
|
+
return f"{os['name']}-{os['version']}-{os['version_codename']}"
|
|
415
|
+
return None
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
def get_os_id_by_name(os_images: list, os_name: str) -> int:
|
|
419
|
+
"""Return OS image ID by name. For example::
|
|
420
|
+
|
|
421
|
+
ubuntu-22.04 --> 79
|
|
422
|
+
windows-2012-standard --> 65
|
|
423
|
+
"""
|
|
424
|
+
os_id = None
|
|
425
|
+
|
|
426
|
+
if os_name.startswith("windows-"):
|
|
427
|
+
name, version, codename = os_name.split("-")
|
|
428
|
+
for os in os_images:
|
|
429
|
+
if (
|
|
430
|
+
os["name"] == name
|
|
431
|
+
and os["version"] == version
|
|
432
|
+
and os["version_codename"] == codename
|
|
433
|
+
):
|
|
434
|
+
os_id = os["id"]
|
|
435
|
+
else:
|
|
436
|
+
name, version = os_name.split("-")
|
|
437
|
+
for os in os_images:
|
|
438
|
+
if os["name"] == name and os["version"] == version:
|
|
439
|
+
os_id = os["id"]
|
|
440
|
+
return os_id
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
def size_to_mb(size: str) -> int:
|
|
444
|
+
"""Transform string like '5G' into integer in megabytes.
|
|
445
|
+
Case insensitive. For example::
|
|
446
|
+
|
|
447
|
+
1T --> 1048576
|
|
448
|
+
5G --> 5120
|
|
449
|
+
1024M --> 1024
|
|
450
|
+
2048 --> 2048
|
|
451
|
+
|
|
452
|
+
NOTE! This function does not support floats e.g. 1.5T x--> 1572864
|
|
453
|
+
"""
|
|
454
|
+
match = re.match(r"^([0-9]+)([mgt]?)$", size, re.I)
|
|
455
|
+
if match:
|
|
456
|
+
try:
|
|
457
|
+
val, unit = list(match.groups())
|
|
458
|
+
if unit.lower() == "g":
|
|
459
|
+
return int(val) * 1024
|
|
460
|
+
if unit.lower() == "t":
|
|
461
|
+
return int(val) * 1048576
|
|
462
|
+
return int(val)
|
|
463
|
+
except TypeError:
|
|
464
|
+
return None
|
|
465
|
+
else:
|
|
466
|
+
return None
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
def check_value(
|
|
470
|
+
value: int, minv: int = 0, maxv: int = 0, step: int = 0
|
|
471
|
+
) -> bool:
|
|
472
|
+
"""Check integer `value` is suitable with required limitations.
|
|
473
|
+
Return True if success. This function is for value testing by
|
|
474
|
+
`server_configurators` requirements.
|
|
475
|
+
"""
|
|
476
|
+
try:
|
|
477
|
+
# pylint: disable=chained-comparison
|
|
478
|
+
return value <= maxv and value >= minv and (value / step).is_integer()
|
|
479
|
+
except TypeError:
|
|
480
|
+
return None
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
def validate_bandwidth(ctx, param, value):
|
|
484
|
+
"""Return valid bandwidth value or exit. See "Callback for Validation"
|
|
485
|
+
at https://click.palletsprojects.com/en/8.1.x/options
|
|
486
|
+
"""
|
|
487
|
+
if not value:
|
|
488
|
+
return None
|
|
489
|
+
|
|
490
|
+
if check_value(value, minv=100, maxv=1000, step=100):
|
|
491
|
+
return value
|
|
492
|
+
|
|
493
|
+
raise click.BadParameter("Value must be in range 100-1000 with step 100.")
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
def validate_image(client, image: str) -> int:
|
|
497
|
+
"""Return valid os_id or exit."""
|
|
498
|
+
log("Get list of OS images...")
|
|
499
|
+
os_images = _server_os_images(client).json()["servers_os"]
|
|
500
|
+
|
|
501
|
+
if re.match(r"^[a-z]+-[0-9.]+$", image, re.I):
|
|
502
|
+
return get_os_id_by_name(os_images, image)
|
|
503
|
+
|
|
504
|
+
try:
|
|
505
|
+
if int(image) in [int(os["id"]) for os in os_images]:
|
|
506
|
+
return int(image)
|
|
507
|
+
return None
|
|
508
|
+
except (TypeError, ValueError):
|
|
509
|
+
return None
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
def validate_cpu(configurator: dict, value: int) -> int:
|
|
513
|
+
"""Return valid cpu value or exit."""
|
|
514
|
+
if check_value(
|
|
515
|
+
value,
|
|
516
|
+
minv=configurator["requirements"]["cpu_min"],
|
|
517
|
+
maxv=configurator["requirements"]["cpu_max"],
|
|
518
|
+
step=configurator["requirements"]["cpu_step"],
|
|
519
|
+
):
|
|
520
|
+
return value
|
|
521
|
+
|
|
522
|
+
raise click.BadParameter("Too many or too few CPUs.")
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
def validate_ram(configurator: dict, value: str) -> int:
|
|
526
|
+
"""Return valid RAM value in megabytes or exit."""
|
|
527
|
+
if check_value(
|
|
528
|
+
size_to_mb(value),
|
|
529
|
+
minv=configurator["requirements"]["ram_min"],
|
|
530
|
+
maxv=configurator["requirements"]["ram_max"],
|
|
531
|
+
step=configurator["requirements"]["ram_step"],
|
|
532
|
+
):
|
|
533
|
+
return size_to_mb(value)
|
|
534
|
+
|
|
535
|
+
raise click.BadParameter("Too large or too small size of RAM.")
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
def validate_disk(configurator: dict, value: str) -> int:
|
|
539
|
+
"""Return valid disk value in megabytes or exit."""
|
|
540
|
+
if check_value(
|
|
541
|
+
size_to_mb(value),
|
|
542
|
+
minv=configurator["requirements"]["disk_min"],
|
|
543
|
+
maxv=configurator["requirements"]["disk_max"],
|
|
544
|
+
step=configurator["requirements"]["disk_step"],
|
|
545
|
+
):
|
|
546
|
+
return size_to_mb(value)
|
|
547
|
+
|
|
548
|
+
raise click.BadParameter("Too large or too small disk size.")
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
def get_configuration(
|
|
552
|
+
client, configurator_id: int, cpu: int, ram: str, disk: str
|
|
553
|
+
) -> dict:
|
|
554
|
+
"""Return `configuration` if CPU, RAM and Disk values is valid or exit.
|
|
555
|
+
This function is used into server_create().
|
|
556
|
+
"""
|
|
557
|
+
configurators = _get_server_configurators(client).json()
|
|
558
|
+
|
|
559
|
+
for item in configurators["server_configurators"]:
|
|
560
|
+
if item["id"] == configurator_id:
|
|
561
|
+
configurator = item # <-- current configurator
|
|
562
|
+
|
|
563
|
+
return {
|
|
564
|
+
"configurator_id": configurator_id,
|
|
565
|
+
"cpu": validate_cpu(configurator, cpu),
|
|
566
|
+
"ram": validate_ram(configurator, ram),
|
|
567
|
+
"disk": validate_disk(configurator, disk),
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
def add_ssh_key_from_file(
|
|
572
|
+
client, ssh_key_file: str, existing_ssh_keys: list
|
|
573
|
+
) -> int:
|
|
574
|
+
"""Return integer SSH-key ID. Add new SSH-key if not exist."""
|
|
575
|
+
ssh_key_name = os.path.basename(ssh_key_file)
|
|
576
|
+
try:
|
|
577
|
+
with open(ssh_key_file, "r", encoding="utf-8") as pubkey:
|
|
578
|
+
ssh_key_body = pubkey.read().strip()
|
|
579
|
+
except (OSError, IOError, FileNotFoundError) as error:
|
|
580
|
+
sys.exit(f"Error: Cannot read SSH-key: {error}")
|
|
581
|
+
|
|
582
|
+
# I don't want to add the same key over and over
|
|
583
|
+
for exist_key in existing_ssh_keys:
|
|
584
|
+
if ssh_key_body == exist_key["body"]:
|
|
585
|
+
log(
|
|
586
|
+
f"SSH-Key '{ssh_key_name}' already exists,"
|
|
587
|
+
f" ID {exist_key['id']} is used."
|
|
588
|
+
)
|
|
589
|
+
return exist_key["id"]
|
|
590
|
+
|
|
591
|
+
log(f"Add new SSH-key '{ssh_key_name}'...")
|
|
592
|
+
added_key = _ssh_key_new(
|
|
593
|
+
client,
|
|
594
|
+
name=ssh_key_name,
|
|
595
|
+
body=ssh_key_body,
|
|
596
|
+
is_default=False,
|
|
597
|
+
)
|
|
598
|
+
ssh_key_id = added_key.json()["ssh_key"]["id"]
|
|
599
|
+
log(f"New SSH-key '{ssh_key_name} ID is '{ssh_key_id}'")
|
|
600
|
+
return ssh_key_id
|
|
601
|
+
|
|
602
|
+
|
|
603
|
+
def add_ssh_key(client, existing_ssh_keys: list, pubkey: str) -> int:
|
|
604
|
+
"""Retrun SSH-key ID. from file, by SSH-key ID or by SSH-key name."""
|
|
605
|
+
# From filesystem
|
|
606
|
+
if os.path.exists(pubkey):
|
|
607
|
+
log(f"SSH-key to add: file: {pubkey}")
|
|
608
|
+
return add_ssh_key_from_file(client, pubkey, existing_ssh_keys)
|
|
609
|
+
|
|
610
|
+
# Add by ID
|
|
611
|
+
if pubkey.isdigit():
|
|
612
|
+
if int(pubkey) in [key["id"] for key in existing_ssh_keys]:
|
|
613
|
+
log(f"SSH-key to add: ID: {pubkey}")
|
|
614
|
+
return int(pubkey)
|
|
615
|
+
sys.exit(f"Error: SSH-key with ID {pubkey} not found.")
|
|
616
|
+
|
|
617
|
+
# Add by name
|
|
618
|
+
for ssh_key in existing_ssh_keys:
|
|
619
|
+
if pubkey == ssh_key["name"]:
|
|
620
|
+
log(f"SSH-key to add: name: {pubkey} ID: {ssh_key['id']}")
|
|
621
|
+
return ssh_key["id"]
|
|
622
|
+
|
|
623
|
+
sys.exit(f"Error: SSH-key '{pubkey}' not found.")
|
|
624
|
+
|
|
625
|
+
|
|
626
|
+
@server.command("create", help="Create Cloud Server.")
|
|
627
|
+
@options(GLOBAL_OPTIONS)
|
|
628
|
+
@options(OUTPUT_FORMAT_OPTION)
|
|
629
|
+
@click.option("--name", required=True, help="Cloud Server display name.")
|
|
630
|
+
@click.option("--comment", help="Comment.")
|
|
631
|
+
@click.option("--avatar-id", default=None, help="Avatar ID.")
|
|
632
|
+
@click.option("--image", required=True, help="OS image to install.")
|
|
633
|
+
@click.option(
|
|
634
|
+
"--preset-id",
|
|
635
|
+
type=int,
|
|
636
|
+
cls=MutuallyExclusiveOption,
|
|
637
|
+
mutually_exclusive=["cpu", "ram", "disk"],
|
|
638
|
+
help="Configuration preset ID.",
|
|
639
|
+
)
|
|
640
|
+
@click.option("--cpu", type=int, help="Number of CPUs.")
|
|
641
|
+
@click.option("--ram", help="RAM size, e.g. 1024M, 1G.")
|
|
642
|
+
@click.option("--disk", help="Primary disk size e.g. 15360M, 15G.")
|
|
643
|
+
@click.option(
|
|
644
|
+
"--bandwidth",
|
|
645
|
+
type=int,
|
|
646
|
+
callback=validate_bandwidth,
|
|
647
|
+
help="Network bandwidth.",
|
|
648
|
+
)
|
|
649
|
+
@click.option(
|
|
650
|
+
"--software-id", type=int, default=None, help="Software ID to install."
|
|
651
|
+
)
|
|
652
|
+
@click.option(
|
|
653
|
+
"--ssh-key",
|
|
654
|
+
metavar="FILE|ID|NAME",
|
|
655
|
+
default=None,
|
|
656
|
+
multiple=True,
|
|
657
|
+
help="SSH-key, can be multiple.",
|
|
658
|
+
)
|
|
659
|
+
@click.option(
|
|
660
|
+
"--ddos-protection",
|
|
661
|
+
type=bool,
|
|
662
|
+
default=False,
|
|
663
|
+
show_default=True,
|
|
664
|
+
help="Enable DDoS-Guard.",
|
|
665
|
+
)
|
|
666
|
+
@click.option(
|
|
667
|
+
"--local-network",
|
|
668
|
+
type=bool,
|
|
669
|
+
default=False,
|
|
670
|
+
show_default=True,
|
|
671
|
+
help="Enable local network.",
|
|
672
|
+
)
|
|
673
|
+
def server_create(
|
|
674
|
+
config,
|
|
675
|
+
profile,
|
|
676
|
+
verbose,
|
|
677
|
+
output_format,
|
|
678
|
+
name,
|
|
679
|
+
comment,
|
|
680
|
+
avatar_id,
|
|
681
|
+
image,
|
|
682
|
+
preset_id,
|
|
683
|
+
cpu,
|
|
684
|
+
ram,
|
|
685
|
+
disk,
|
|
686
|
+
bandwidth,
|
|
687
|
+
software_id,
|
|
688
|
+
ssh_key,
|
|
689
|
+
ddos_protection,
|
|
690
|
+
local_network,
|
|
691
|
+
):
|
|
692
|
+
"""Create Cloud Server."""
|
|
693
|
+
# pylint: disable=too-many-locals
|
|
694
|
+
|
|
695
|
+
client = create_client(config, profile)
|
|
696
|
+
|
|
697
|
+
# Get os_id or exit
|
|
698
|
+
log("Looking for os_id...")
|
|
699
|
+
os_id = validate_image(client, image)
|
|
700
|
+
log(f"os_id is {os_id}")
|
|
701
|
+
if not os_id:
|
|
702
|
+
raise click.BadParameter("Wrong image name or ID.")
|
|
703
|
+
|
|
704
|
+
# Fallback bandwidth to minimum
|
|
705
|
+
if not bandwidth and not preset_id:
|
|
706
|
+
bandwidth = 100
|
|
707
|
+
|
|
708
|
+
# SSH-keys
|
|
709
|
+
ssh_keys_ids = []
|
|
710
|
+
log("Get SSH-keys...")
|
|
711
|
+
existing_ssh_keys = _ssh_key_list(client).json()["ssh_keys"]
|
|
712
|
+
|
|
713
|
+
for pubkey in ssh_key:
|
|
714
|
+
ssh_keys_ids.append(add_ssh_key(client, existing_ssh_keys, pubkey))
|
|
715
|
+
|
|
716
|
+
# Create Cloud Server from configurator or preset
|
|
717
|
+
if cpu or ram or disk:
|
|
718
|
+
log("Get configurator...")
|
|
719
|
+
configuration = get_configuration(
|
|
720
|
+
client,
|
|
721
|
+
DEFAULT_CONFIGURATOR_ID,
|
|
722
|
+
cpu,
|
|
723
|
+
ram,
|
|
724
|
+
disk,
|
|
725
|
+
)
|
|
726
|
+
|
|
727
|
+
# Do request
|
|
728
|
+
log("Create Cloud Server with configurator...")
|
|
729
|
+
response = _server_create(
|
|
730
|
+
client,
|
|
731
|
+
configuration=configuration,
|
|
732
|
+
os_id=os_id,
|
|
733
|
+
bandwidth=bandwidth,
|
|
734
|
+
name=name,
|
|
735
|
+
is_ddos_guard=ddos_protection,
|
|
736
|
+
is_local_network=local_network,
|
|
737
|
+
comment=comment,
|
|
738
|
+
software_id=software_id,
|
|
739
|
+
avatar_id=avatar_id,
|
|
740
|
+
ssh_keys_ids=ssh_keys_ids,
|
|
741
|
+
)
|
|
742
|
+
elif preset_id:
|
|
743
|
+
# Set bandwidth value from preset if option is not set
|
|
744
|
+
if not bandwidth:
|
|
745
|
+
log("Check preset_id...")
|
|
746
|
+
presets = _server_presets(client).json()["server_presets"]
|
|
747
|
+
for preset in presets:
|
|
748
|
+
if preset["id"] == preset_id:
|
|
749
|
+
log(f"Set bandwidth from preset: {preset['bandwidth']}")
|
|
750
|
+
bandwidth = preset["bandwidth"]
|
|
751
|
+
|
|
752
|
+
# Do request
|
|
753
|
+
log(f"Create Cloud Server with preset_id {preset_id}...")
|
|
754
|
+
response = _server_create(
|
|
755
|
+
client,
|
|
756
|
+
preset_id=preset_id,
|
|
757
|
+
os_id=os_id,
|
|
758
|
+
bandwidth=bandwidth,
|
|
759
|
+
name=name,
|
|
760
|
+
is_ddos_guard=ddos_protection,
|
|
761
|
+
is_local_network=local_network,
|
|
762
|
+
comment=comment,
|
|
763
|
+
software_id=software_id,
|
|
764
|
+
avatar_id=avatar_id,
|
|
765
|
+
ssh_keys_ids=ssh_keys_ids,
|
|
766
|
+
)
|
|
767
|
+
else:
|
|
768
|
+
raise click.UsageError(
|
|
769
|
+
"Configuration or preset is required. "
|
|
770
|
+
"Set '--cpu', '--ram' and '--disk' or '--preset-id'"
|
|
771
|
+
)
|
|
772
|
+
|
|
773
|
+
fmt.printer(
|
|
774
|
+
response,
|
|
775
|
+
output_format=output_format,
|
|
776
|
+
func=lambda response: click.echo(response.json()["server"]["id"]),
|
|
777
|
+
)
|
|
778
|
+
|
|
779
|
+
|
|
780
|
+
# ------------------------------------------------------------- #
|
|
781
|
+
# $ twc server set-property #
|
|
782
|
+
# ------------------------------------------------------------- #
|
|
783
|
+
|
|
784
|
+
|
|
785
|
+
@server.command("set-property", help="Update Cloud Server properties.")
|
|
786
|
+
@options(GLOBAL_OPTIONS)
|
|
787
|
+
@options(OUTPUT_FORMAT_OPTION)
|
|
788
|
+
@click.option("--name", help="Cloud server display name.")
|
|
789
|
+
@click.option("--comment", help="Comment.")
|
|
790
|
+
@click.option("--avatar-id", default=None, help="Avatar ID.")
|
|
791
|
+
@click.argument("server_id", type=int, required=True)
|
|
792
|
+
def server_set_property(
|
|
793
|
+
config,
|
|
794
|
+
profile,
|
|
795
|
+
verbose,
|
|
796
|
+
output_format,
|
|
797
|
+
name,
|
|
798
|
+
comment,
|
|
799
|
+
avatar_id,
|
|
800
|
+
server_id,
|
|
801
|
+
):
|
|
802
|
+
client = create_client(config, profile)
|
|
803
|
+
payload = {}
|
|
804
|
+
|
|
805
|
+
if name:
|
|
806
|
+
payload.update({"name": name})
|
|
807
|
+
if comment:
|
|
808
|
+
payload.update({"comment": comment})
|
|
809
|
+
if avatar_id:
|
|
810
|
+
payload.update({"avatar_id": avatar_id})
|
|
811
|
+
|
|
812
|
+
if not payload:
|
|
813
|
+
raise click.UsageError(
|
|
814
|
+
"Nothing to do. Set one of "
|
|
815
|
+
"['--name', '--comment', '--avatar-id']"
|
|
816
|
+
)
|
|
817
|
+
|
|
818
|
+
response = _server_update(client, server_id, payload)
|
|
819
|
+
fmt.printer(
|
|
820
|
+
response,
|
|
821
|
+
output_format=output_format,
|
|
822
|
+
func=lambda response: click.echo(response.json()["server"]["id"]),
|
|
823
|
+
)
|
|
824
|
+
|
|
825
|
+
|
|
826
|
+
# ------------------------------------------------------------- #
|
|
827
|
+
# $ twc server resize #
|
|
828
|
+
# ------------------------------------------------------------- #
|
|
829
|
+
|
|
830
|
+
|
|
831
|
+
@server.command("resize", help="Change CPU, RAM, disk and bandwidth.")
|
|
832
|
+
@options(GLOBAL_OPTIONS)
|
|
833
|
+
@options(OUTPUT_FORMAT_OPTION)
|
|
834
|
+
@click.option(
|
|
835
|
+
"--preset-id",
|
|
836
|
+
type=int,
|
|
837
|
+
cls=MutuallyExclusiveOption,
|
|
838
|
+
mutually_exclusive=["cpu", "ram", "disk"],
|
|
839
|
+
help="Configuration preset ID.",
|
|
840
|
+
)
|
|
841
|
+
@click.option("--cpu", type=int, help="Number of vCPUs.")
|
|
842
|
+
@click.option("--ram", help="RAM size, e.g. 1024M, 1G.")
|
|
843
|
+
@click.option("--disk", help="Primary disk size e.g. 15360M, 15G.")
|
|
844
|
+
@click.option(
|
|
845
|
+
"--bandwidth",
|
|
846
|
+
type=int,
|
|
847
|
+
callback=validate_bandwidth,
|
|
848
|
+
help="Network bandwidth.",
|
|
849
|
+
)
|
|
850
|
+
@click.option(
|
|
851
|
+
"--yes",
|
|
852
|
+
"confirmed",
|
|
853
|
+
is_flag=True,
|
|
854
|
+
help="Confirm the action without prompting.",
|
|
855
|
+
)
|
|
856
|
+
@click.argument("server_id", type=int, required=True)
|
|
857
|
+
def server_resize(
|
|
858
|
+
config,
|
|
859
|
+
profile,
|
|
860
|
+
verbose,
|
|
861
|
+
output_format,
|
|
862
|
+
preset_id,
|
|
863
|
+
cpu,
|
|
864
|
+
ram,
|
|
865
|
+
disk,
|
|
866
|
+
bandwidth,
|
|
867
|
+
confirmed,
|
|
868
|
+
server_id,
|
|
869
|
+
):
|
|
870
|
+
"""Resize Cloud Server CPU, RAM and primary disk size."""
|
|
871
|
+
# pylint: disable=too-many-locals
|
|
872
|
+
# pylint: disable=too-many-branches
|
|
873
|
+
# pylint: disable=too-many-statements
|
|
874
|
+
|
|
875
|
+
client = create_client(config, profile)
|
|
876
|
+
payload = {}
|
|
877
|
+
|
|
878
|
+
# Save original server state
|
|
879
|
+
log("Get server original state...")
|
|
880
|
+
old_state = _server_get(client, server_id).json()["server"]
|
|
881
|
+
|
|
882
|
+
# Get original server preset tags
|
|
883
|
+
old_preset_tags = []
|
|
884
|
+
if old_state["preset_id"]:
|
|
885
|
+
log(f"Get preset tags by preset_id {old_state['preset_id']}...")
|
|
886
|
+
presets = _server_presets(client).json()["server_presets"]
|
|
887
|
+
for preset in presets:
|
|
888
|
+
if preset["id"] == old_state["preset_id"]:
|
|
889
|
+
old_preset_tags = preset["tags"]
|
|
890
|
+
log(f"Preset tags is {old_preset_tags}")
|
|
891
|
+
|
|
892
|
+
# Return error if user tries to change dedicated server
|
|
893
|
+
if "vds_dedic" in old_preset_tags:
|
|
894
|
+
sys.exit(
|
|
895
|
+
"Error: Cannot change dedicated server."
|
|
896
|
+
+ "Please contact techsupport."
|
|
897
|
+
)
|
|
898
|
+
|
|
899
|
+
# Handle case: user tries to change configurator or switch from
|
|
900
|
+
# preset to configurator.
|
|
901
|
+
|
|
902
|
+
# Return error if user tries to switch from preset to configurator in
|
|
903
|
+
# location where configurator is unavailable.
|
|
904
|
+
if cpu or ram or disk:
|
|
905
|
+
if old_state["location"] not in REGIONS_WITH_CONFIGURATOR:
|
|
906
|
+
sys.exit(
|
|
907
|
+
"Error: Can not change configuration in location "
|
|
908
|
+
+ f"'{old_state['location']}'. Change preset_id instead."
|
|
909
|
+
)
|
|
910
|
+
|
|
911
|
+
# Get original server configurator_id
|
|
912
|
+
configurator_id = old_state["configurator_id"]
|
|
913
|
+
configurator = None
|
|
914
|
+
|
|
915
|
+
# Get configurator_id if user tries to switch from preset to
|
|
916
|
+
# configurator. Don't ask what is this.
|
|
917
|
+
log("Get configurator_id...")
|
|
918
|
+
if not configurator_id:
|
|
919
|
+
if (
|
|
920
|
+
"ssd_2022" in old_preset_tags
|
|
921
|
+
or "discount35" in old_preset_tags
|
|
922
|
+
):
|
|
923
|
+
configurator_id = 11 # discount configurator
|
|
924
|
+
else:
|
|
925
|
+
configurator_id = 9 # old full price configurator
|
|
926
|
+
|
|
927
|
+
# Get configurator by configurator_id
|
|
928
|
+
log(f"configurator_id is {configurator_id}, get confugurator...")
|
|
929
|
+
configurators = _get_server_configurators(client).json()
|
|
930
|
+
for item in configurators["server_configurators"]:
|
|
931
|
+
if item["id"] == configurator_id:
|
|
932
|
+
configurator = item # <-- this!
|
|
933
|
+
|
|
934
|
+
# Check configurator and return error if configurator is unavailable
|
|
935
|
+
log(f"Configurator: '{configurator}'")
|
|
936
|
+
if configurator_id and not configurator:
|
|
937
|
+
sys.exit(
|
|
938
|
+
"Error: Configurator is not available for your server. "
|
|
939
|
+
+ "Try to create new server."
|
|
940
|
+
)
|
|
941
|
+
|
|
942
|
+
# Add configurator key to payload
|
|
943
|
+
payload.update({"configurator": {}})
|
|
944
|
+
|
|
945
|
+
# Get original size of primary disk
|
|
946
|
+
for old_disk in old_state["disks"]:
|
|
947
|
+
if old_disk["is_system"]: # is True
|
|
948
|
+
primary_disk_size = old_disk["size"]
|
|
949
|
+
|
|
950
|
+
# Fill payload with original server specs
|
|
951
|
+
payload["configurator"].update(
|
|
952
|
+
{
|
|
953
|
+
"configurator_id": configurator_id,
|
|
954
|
+
"cpu": old_state["cpu"],
|
|
955
|
+
"ram": old_state["ram"],
|
|
956
|
+
"disk": primary_disk_size,
|
|
957
|
+
}
|
|
958
|
+
)
|
|
959
|
+
|
|
960
|
+
# Refill payload with parameters from command line
|
|
961
|
+
if cpu:
|
|
962
|
+
payload["configurator"].update(
|
|
963
|
+
{"cpu": validate_cpu(configurator, cpu)}
|
|
964
|
+
)
|
|
965
|
+
|
|
966
|
+
if ram:
|
|
967
|
+
payload["configurator"].update(
|
|
968
|
+
{"ram": validate_ram(configurator, ram)}
|
|
969
|
+
)
|
|
970
|
+
|
|
971
|
+
if disk:
|
|
972
|
+
payload["configurator"].update(
|
|
973
|
+
{"disk": validate_disk(configurator, disk)}
|
|
974
|
+
)
|
|
975
|
+
|
|
976
|
+
if bandwidth:
|
|
977
|
+
payload.update({"bandwidth": bandwidth})
|
|
978
|
+
|
|
979
|
+
# Handle case: user tries to change preset to another preset.
|
|
980
|
+
# Check passed preset_id and exit on fail
|
|
981
|
+
if preset_id:
|
|
982
|
+
# I cannot want to change preset to itself
|
|
983
|
+
if preset_id == old_state["preset_id"]:
|
|
984
|
+
sys.exit(
|
|
985
|
+
"Error: Cannot change preset to itself. "
|
|
986
|
+
f"Server already have preset_id {old_state['preset_id']}."
|
|
987
|
+
)
|
|
988
|
+
|
|
989
|
+
presets = _server_presets(client).json()["server_presets"]
|
|
990
|
+
for preset in presets:
|
|
991
|
+
if preset["id"] == preset_id:
|
|
992
|
+
payload.update({"preset_id": preset_id})
|
|
993
|
+
try:
|
|
994
|
+
payload["preset_id"]
|
|
995
|
+
except KeyError:
|
|
996
|
+
sys.exit(f"Error: Invalid preset_id {preset_id}")
|
|
997
|
+
|
|
998
|
+
# Prompt if no option --yes passed
|
|
999
|
+
if not confirmed:
|
|
1000
|
+
if not confirm_action("Server will restart, continue?"):
|
|
1001
|
+
sys.exit("Aborted!")
|
|
1002
|
+
|
|
1003
|
+
# Make request
|
|
1004
|
+
response = _server_update(client, server_id, payload)
|
|
1005
|
+
fmt.printer(
|
|
1006
|
+
response,
|
|
1007
|
+
output_format=output_format,
|
|
1008
|
+
func=lambda response: click.echo(response.json()["server"]["id"]),
|
|
1009
|
+
)
|
|
1010
|
+
|
|
1011
|
+
|
|
1012
|
+
# ------------------------------------------------------------- #
|
|
1013
|
+
# $ twc server reinstall #
|
|
1014
|
+
# ------------------------------------------------------------- #
|
|
1015
|
+
|
|
1016
|
+
|
|
1017
|
+
@server.command("reinstall", help="Reinstall OS or software.")
|
|
1018
|
+
@options(GLOBAL_OPTIONS)
|
|
1019
|
+
@options(OUTPUT_FORMAT_OPTION)
|
|
1020
|
+
@click.option("--image", default=None, help="OS image to install.")
|
|
1021
|
+
@click.option(
|
|
1022
|
+
"--software-id", type=int, default=None, help="Software ID to install."
|
|
1023
|
+
)
|
|
1024
|
+
@click.confirmation_option(
|
|
1025
|
+
prompt="All data on Cloud Server will be lost.\n"
|
|
1026
|
+
"This action cannot be undone. Are you sure?"
|
|
1027
|
+
)
|
|
1028
|
+
@click.argument("server_id", type=int, required=True)
|
|
1029
|
+
def server_reinstall(
|
|
1030
|
+
config,
|
|
1031
|
+
profile,
|
|
1032
|
+
verbose,
|
|
1033
|
+
output_format,
|
|
1034
|
+
image,
|
|
1035
|
+
software_id,
|
|
1036
|
+
server_id,
|
|
1037
|
+
):
|
|
1038
|
+
client = create_client(config, profile)
|
|
1039
|
+
payload = {}
|
|
1040
|
+
|
|
1041
|
+
if image:
|
|
1042
|
+
os_id = validate_image(client, image)
|
|
1043
|
+
if not os_id:
|
|
1044
|
+
raise click.BadParameter("Wrong image name or ID.")
|
|
1045
|
+
payload.update({"os_id": os_id})
|
|
1046
|
+
|
|
1047
|
+
if software_id:
|
|
1048
|
+
old_state = _server_get(client, server_id).json()["server"]
|
|
1049
|
+
payload.update(
|
|
1050
|
+
{
|
|
1051
|
+
"os_id": old_state["os"]["id"],
|
|
1052
|
+
"software_id": software_id,
|
|
1053
|
+
}
|
|
1054
|
+
)
|
|
1055
|
+
|
|
1056
|
+
if not payload:
|
|
1057
|
+
raise click.UsageError(
|
|
1058
|
+
"Nothing to do. Set one of ['--image', '--software-id']"
|
|
1059
|
+
)
|
|
1060
|
+
|
|
1061
|
+
response = _server_update(client, server_id, payload)
|
|
1062
|
+
fmt.printer(
|
|
1063
|
+
response,
|
|
1064
|
+
output_format=output_format,
|
|
1065
|
+
func=lambda response: click.echo(response.json()["server"]["id"]),
|
|
1066
|
+
)
|
|
1067
|
+
|
|
1068
|
+
|
|
1069
|
+
# ------------------------------------------------------------- #
|
|
1070
|
+
# $ twc server <action> #
|
|
1071
|
+
# remove, boot, reboot, shutdown, clone, reset-root-password #
|
|
1072
|
+
# ------------------------------------------------------------- #
|
|
1073
|
+
|
|
1074
|
+
# ------------------------------------------------------------- #
|
|
1075
|
+
# $ twc server remove #
|
|
1076
|
+
# ------------------------------------------------------------- #
|
|
1077
|
+
|
|
1078
|
+
|
|
1079
|
+
@server.command("remove", aliases=["rm"], help="Remove Cloud Server.")
|
|
1080
|
+
@options(GLOBAL_OPTIONS)
|
|
1081
|
+
@click.argument("server_ids", nargs=-1, type=int, required=True)
|
|
1082
|
+
@click.confirmation_option(
|
|
1083
|
+
prompt="This action cannot be undone. Are you sure?"
|
|
1084
|
+
)
|
|
1085
|
+
def server_remove(config, profile, verbose, server_ids):
|
|
1086
|
+
client = create_client(config, profile)
|
|
1087
|
+
|
|
1088
|
+
for server_id in server_ids:
|
|
1089
|
+
response = _server_remove(client, server_id)
|
|
1090
|
+
if response.status_code == 204:
|
|
1091
|
+
click.echo(server_id)
|
|
1092
|
+
else:
|
|
1093
|
+
fmt.printer(response)
|
|
1094
|
+
|
|
1095
|
+
|
|
1096
|
+
# ------------------------------------------------------------- #
|
|
1097
|
+
# $ twc server boot #
|
|
1098
|
+
# ------------------------------------------------------------- #
|
|
1099
|
+
|
|
1100
|
+
|
|
1101
|
+
@server.command("boot", aliases=["start"], help="Boot Cloud Server.")
|
|
1102
|
+
@options(GLOBAL_OPTIONS)
|
|
1103
|
+
@click.argument("server_ids", nargs=-1, type=int, required=True)
|
|
1104
|
+
def server_start(config, profile, verbose, server_ids):
|
|
1105
|
+
client = create_client(config, profile)
|
|
1106
|
+
|
|
1107
|
+
for server_id in server_ids:
|
|
1108
|
+
response = _server_action(client, server_id, action="start")
|
|
1109
|
+
if response.status_code == 204:
|
|
1110
|
+
click.echo(server_id)
|
|
1111
|
+
else:
|
|
1112
|
+
fmt.printer(response)
|
|
1113
|
+
|
|
1114
|
+
|
|
1115
|
+
# ------------------------------------------------------------- #
|
|
1116
|
+
# $ twc server reboot #
|
|
1117
|
+
# ------------------------------------------------------------- #
|
|
1118
|
+
|
|
1119
|
+
|
|
1120
|
+
@server.command("reboot", aliases=["restart"], help="Reboot Cloud Server.")
|
|
1121
|
+
@options(GLOBAL_OPTIONS)
|
|
1122
|
+
@click.option("--hard", "hard_reboot", is_flag=True, help="Do hard reboot.")
|
|
1123
|
+
@click.argument("server_ids", nargs=-1, type=int, required=True)
|
|
1124
|
+
def server_reboot(config, profile, verbose, server_ids, hard_reboot):
|
|
1125
|
+
if hard_reboot:
|
|
1126
|
+
action = "hard_reboot"
|
|
1127
|
+
else:
|
|
1128
|
+
action = "boot"
|
|
1129
|
+
client = create_client(config, profile)
|
|
1130
|
+
|
|
1131
|
+
for server_id in server_ids:
|
|
1132
|
+
response = _server_action(client, server_id, action=action)
|
|
1133
|
+
if response.status_code == 204:
|
|
1134
|
+
click.echo(server_id)
|
|
1135
|
+
else:
|
|
1136
|
+
fmt.printer(response)
|
|
1137
|
+
|
|
1138
|
+
|
|
1139
|
+
# ------------------------------------------------------------- #
|
|
1140
|
+
# $ twc server shutdown #
|
|
1141
|
+
# ------------------------------------------------------------- #
|
|
1142
|
+
|
|
1143
|
+
|
|
1144
|
+
@server.command("shutdown", aliases=["stop"], help="Shutdown Cloud Server.")
|
|
1145
|
+
@options(GLOBAL_OPTIONS)
|
|
1146
|
+
@click.option(
|
|
1147
|
+
"--hard", "hard_shutdown", is_flag=True, help="Do hard shutdown."
|
|
1148
|
+
)
|
|
1149
|
+
@click.argument("server_ids", nargs=-1, type=int, required=True)
|
|
1150
|
+
def server_shutdown(config, profile, verbose, server_ids, hard_shutdown):
|
|
1151
|
+
if hard_shutdown:
|
|
1152
|
+
action = "hard_shutdown"
|
|
1153
|
+
else:
|
|
1154
|
+
action = "shutdown"
|
|
1155
|
+
client = create_client(config, profile)
|
|
1156
|
+
|
|
1157
|
+
for server_id in server_ids:
|
|
1158
|
+
response = _server_action(client, server_id, action=action)
|
|
1159
|
+
if response.status_code == 204:
|
|
1160
|
+
click.echo(server_id)
|
|
1161
|
+
else:
|
|
1162
|
+
fmt.printer(response)
|
|
1163
|
+
|
|
1164
|
+
|
|
1165
|
+
# ------------------------------------------------------------- #
|
|
1166
|
+
# $ twc server clone #
|
|
1167
|
+
# ------------------------------------------------------------- #
|
|
1168
|
+
|
|
1169
|
+
|
|
1170
|
+
@server.command("clone", help="Clone Cloud Server.")
|
|
1171
|
+
@options(GLOBAL_OPTIONS)
|
|
1172
|
+
@click.argument("server_id", type=int, required=True)
|
|
1173
|
+
def server_clone(config, profile, verbose, server_id):
|
|
1174
|
+
client = create_client(config, profile)
|
|
1175
|
+
response = _server_action(client, server_id, action="clone")
|
|
1176
|
+
if response.status_code == 204:
|
|
1177
|
+
click.echo(server_id)
|
|
1178
|
+
else:
|
|
1179
|
+
fmt.printer(response)
|
|
1180
|
+
|
|
1181
|
+
|
|
1182
|
+
# ------------------------------------------------------------- #
|
|
1183
|
+
# $ twc server reset-root-password #
|
|
1184
|
+
# ------------------------------------------------------------- #
|
|
1185
|
+
|
|
1186
|
+
|
|
1187
|
+
@server.command("reset-root-password", help="Reset root user password.")
|
|
1188
|
+
@options(GLOBAL_OPTIONS)
|
|
1189
|
+
@click.confirmation_option(
|
|
1190
|
+
prompt="New password will sent to contact email. Continue?"
|
|
1191
|
+
)
|
|
1192
|
+
@click.argument("server_id", type=int, required=True)
|
|
1193
|
+
def server_reset_root(config, profile, verbose, server_id):
|
|
1194
|
+
client = create_client(config, profile)
|
|
1195
|
+
response = _server_action(client, server_id, action="reset_password")
|
|
1196
|
+
if response.status_code == 204:
|
|
1197
|
+
click.echo(server_id)
|
|
1198
|
+
else:
|
|
1199
|
+
fmt.printer(response)
|
|
1200
|
+
|
|
1201
|
+
|
|
1202
|
+
# ------------------------------------------------------------- #
|
|
1203
|
+
# $ twc server list-presets #
|
|
1204
|
+
# ------------------------------------------------------------- #
|
|
1205
|
+
|
|
1206
|
+
|
|
1207
|
+
def print_presets(response: object, filters: str):
|
|
1208
|
+
if filters:
|
|
1209
|
+
presets = fmt.filter_list(response.json()["server_presets"], filters)
|
|
1210
|
+
else:
|
|
1211
|
+
presets = response.json()["server_presets"]
|
|
1212
|
+
table = fmt.Table()
|
|
1213
|
+
table.header(
|
|
1214
|
+
[
|
|
1215
|
+
"ID",
|
|
1216
|
+
"REGION",
|
|
1217
|
+
"PRICE",
|
|
1218
|
+
"CPU",
|
|
1219
|
+
"CPU FREQ",
|
|
1220
|
+
"RAM",
|
|
1221
|
+
"DISK",
|
|
1222
|
+
"DISK TYPE",
|
|
1223
|
+
"BANDWIDTH",
|
|
1224
|
+
"DESCRIPTION",
|
|
1225
|
+
"LOCAL NETWORK",
|
|
1226
|
+
]
|
|
1227
|
+
)
|
|
1228
|
+
for preset in presets:
|
|
1229
|
+
table.row(
|
|
1230
|
+
[
|
|
1231
|
+
preset["id"],
|
|
1232
|
+
preset["location"],
|
|
1233
|
+
preset["price"],
|
|
1234
|
+
preset["cpu"],
|
|
1235
|
+
preset["cpu_frequency"],
|
|
1236
|
+
preset["ram"],
|
|
1237
|
+
preset["disk"],
|
|
1238
|
+
preset["disk_type"],
|
|
1239
|
+
preset["bandwidth"],
|
|
1240
|
+
preset["description_short"],
|
|
1241
|
+
preset["is_allowed_local_network"],
|
|
1242
|
+
]
|
|
1243
|
+
)
|
|
1244
|
+
table.print()
|
|
1245
|
+
|
|
1246
|
+
|
|
1247
|
+
@server.command("list-presets", help="List configuration presets.")
|
|
1248
|
+
@options(GLOBAL_OPTIONS)
|
|
1249
|
+
@options(OUTPUT_FORMAT_OPTION)
|
|
1250
|
+
@click.option("--region", "-r", help="Use region (location).")
|
|
1251
|
+
@click.option("--filter", "-f", "filters", default="", help="Filter output.")
|
|
1252
|
+
def server_presets(config, profile, verbose, output_format, filters, region):
|
|
1253
|
+
if filters:
|
|
1254
|
+
filters = filters.replace("region", "location")
|
|
1255
|
+
if region:
|
|
1256
|
+
if filters:
|
|
1257
|
+
filters = filters + f",location:{region}"
|
|
1258
|
+
else:
|
|
1259
|
+
filters = f"location:{region}"
|
|
1260
|
+
|
|
1261
|
+
client = create_client(config, profile)
|
|
1262
|
+
response = _server_presets(client)
|
|
1263
|
+
fmt.printer(
|
|
1264
|
+
response,
|
|
1265
|
+
output_format=output_format,
|
|
1266
|
+
filters=filters,
|
|
1267
|
+
func=print_presets,
|
|
1268
|
+
)
|
|
1269
|
+
|
|
1270
|
+
|
|
1271
|
+
# ------------------------------------------------------------- #
|
|
1272
|
+
# $ twc server list-os-images #
|
|
1273
|
+
# ------------------------------------------------------------- #
|
|
1274
|
+
|
|
1275
|
+
|
|
1276
|
+
def print_os_images(response: object, filters: str):
|
|
1277
|
+
if filters:
|
|
1278
|
+
os_list = fmt.filter_list(response.json()["servers_os"], filters)
|
|
1279
|
+
else:
|
|
1280
|
+
os_list = response.json()["servers_os"]
|
|
1281
|
+
table = fmt.Table()
|
|
1282
|
+
table.header(
|
|
1283
|
+
["ID", "FAMILY", "NAME", "VERSION", "CODENAME", "REQUIREMENTS"]
|
|
1284
|
+
)
|
|
1285
|
+
for os in os_list:
|
|
1286
|
+
try:
|
|
1287
|
+
value = os["requirements"]["disk_min"]
|
|
1288
|
+
requirements = f"disk_min: {value}G"
|
|
1289
|
+
except KeyError:
|
|
1290
|
+
requirements = ""
|
|
1291
|
+
table.row(
|
|
1292
|
+
[
|
|
1293
|
+
os["id"],
|
|
1294
|
+
os["family"],
|
|
1295
|
+
os["name"],
|
|
1296
|
+
os["version"],
|
|
1297
|
+
os["version_codename"],
|
|
1298
|
+
requirements,
|
|
1299
|
+
]
|
|
1300
|
+
)
|
|
1301
|
+
table.print()
|
|
1302
|
+
|
|
1303
|
+
|
|
1304
|
+
@server.command(
|
|
1305
|
+
"list-os-images", help="List prebuilt operating system images."
|
|
1306
|
+
)
|
|
1307
|
+
@options(GLOBAL_OPTIONS)
|
|
1308
|
+
@options(OUTPUT_FORMAT_OPTION)
|
|
1309
|
+
@click.option("--filter", "-f", "filters", default="", help="Filter output.")
|
|
1310
|
+
def server_os_images(config, profile, verbose, output_format, filters):
|
|
1311
|
+
client = create_client(config, profile)
|
|
1312
|
+
response = _server_os_images(client)
|
|
1313
|
+
fmt.printer(
|
|
1314
|
+
response,
|
|
1315
|
+
output_format=output_format,
|
|
1316
|
+
filters=filters,
|
|
1317
|
+
func=print_os_images,
|
|
1318
|
+
)
|
|
1319
|
+
|
|
1320
|
+
|
|
1321
|
+
# ------------------------------------------------------------- #
|
|
1322
|
+
# $ twc server list-software #
|
|
1323
|
+
# ------------------------------------------------------------- #
|
|
1324
|
+
|
|
1325
|
+
|
|
1326
|
+
def print_software(response: object):
|
|
1327
|
+
table = fmt.Table()
|
|
1328
|
+
table.header(["ID", "NAME", "OS"])
|
|
1329
|
+
for soft in response.json()["servers_software"]:
|
|
1330
|
+
table.row(
|
|
1331
|
+
[
|
|
1332
|
+
soft["id"],
|
|
1333
|
+
soft["name"],
|
|
1334
|
+
", ".join([str(k) for k in soft["os_ids"]]),
|
|
1335
|
+
]
|
|
1336
|
+
)
|
|
1337
|
+
table.print()
|
|
1338
|
+
|
|
1339
|
+
|
|
1340
|
+
@server.command("list-software", help="List software.")
|
|
1341
|
+
@options(GLOBAL_OPTIONS)
|
|
1342
|
+
@options(OUTPUT_FORMAT_OPTION)
|
|
1343
|
+
def server_software(config, profile, verbose, output_format):
|
|
1344
|
+
client = create_client(config, profile)
|
|
1345
|
+
response = _server_software(client)
|
|
1346
|
+
fmt.printer(response, output_format=output_format, func=print_software)
|
|
1347
|
+
|
|
1348
|
+
|
|
1349
|
+
# ------------------------------------------------------------- #
|
|
1350
|
+
# $ twc server logs #
|
|
1351
|
+
# ------------------------------------------------------------- #
|
|
1352
|
+
|
|
1353
|
+
|
|
1354
|
+
def print_logs(response: object):
|
|
1355
|
+
event_log = response.json()["server_logs"]
|
|
1356
|
+
for line in event_log:
|
|
1357
|
+
click.echo(
|
|
1358
|
+
f"{line['logged_at']} id={line['id']} event={line['event']}"
|
|
1359
|
+
)
|
|
1360
|
+
|
|
1361
|
+
|
|
1362
|
+
@server.command("logs", help="View Cloud Server events log.")
|
|
1363
|
+
@options(GLOBAL_OPTIONS)
|
|
1364
|
+
@options(OUTPUT_FORMAT_OPTION)
|
|
1365
|
+
@click.option("--limit", default=100, show_default=True, help="Limit.")
|
|
1366
|
+
@click.option(
|
|
1367
|
+
"--order",
|
|
1368
|
+
default="asc",
|
|
1369
|
+
show_default=True,
|
|
1370
|
+
type=click.Choice(["asc", "desc"]),
|
|
1371
|
+
help="Sort logs by datetime.",
|
|
1372
|
+
)
|
|
1373
|
+
@click.argument("server_id", type=int, required=True)
|
|
1374
|
+
def server_logs(
|
|
1375
|
+
config, profile, verbose, output_format, limit, order, server_id
|
|
1376
|
+
):
|
|
1377
|
+
client = create_client(config, profile)
|
|
1378
|
+
response = _server_logs(
|
|
1379
|
+
client, server_id=server_id, limit=limit, order=order
|
|
1380
|
+
)
|
|
1381
|
+
fmt.printer(response, output_format=output_format, func=print_logs)
|
|
1382
|
+
|
|
1383
|
+
|
|
1384
|
+
# ------------------------------------------------------------- #
|
|
1385
|
+
# $ twc server set-boot-mode #
|
|
1386
|
+
# ------------------------------------------------------------- #
|
|
1387
|
+
|
|
1388
|
+
|
|
1389
|
+
@server.command("set-boot-mode", help="Set Cloud Server boot mode.")
|
|
1390
|
+
@options(GLOBAL_OPTIONS)
|
|
1391
|
+
@click.option(
|
|
1392
|
+
"--server-id",
|
|
1393
|
+
"server_ids",
|
|
1394
|
+
type=int,
|
|
1395
|
+
multiple=True,
|
|
1396
|
+
required=True,
|
|
1397
|
+
help="Cloud Server ID, can be multiple.",
|
|
1398
|
+
)
|
|
1399
|
+
@click.confirmation_option(prompt="Server will reboot, continue?")
|
|
1400
|
+
@click.argument(
|
|
1401
|
+
"boot_mode",
|
|
1402
|
+
type=click.Choice(["default", "single", "recovery_disk"]),
|
|
1403
|
+
required=True,
|
|
1404
|
+
)
|
|
1405
|
+
def server_set_boot_mode(config, profile, verbose, boot_mode, server_ids):
|
|
1406
|
+
client = create_client(config, profile)
|
|
1407
|
+
|
|
1408
|
+
for server_id in server_ids:
|
|
1409
|
+
response = _server_set_boot_mode(
|
|
1410
|
+
client, server_id, boot_mode=boot_mode
|
|
1411
|
+
)
|
|
1412
|
+
if response.status_code == 204:
|
|
1413
|
+
click.echo(server_id)
|
|
1414
|
+
else:
|
|
1415
|
+
fmt.printer(response)
|
|
1416
|
+
|
|
1417
|
+
|
|
1418
|
+
# ------------------------------------------------------------- #
|
|
1419
|
+
# $ twc server set-nat-mode #
|
|
1420
|
+
# ------------------------------------------------------------- #
|
|
1421
|
+
|
|
1422
|
+
|
|
1423
|
+
@server.command("set-nat-mode", help="Set Cloud Server NAT mode.")
|
|
1424
|
+
@options(GLOBAL_OPTIONS)
|
|
1425
|
+
@click.option(
|
|
1426
|
+
"--server-id",
|
|
1427
|
+
"server_ids",
|
|
1428
|
+
type=int,
|
|
1429
|
+
multiple=True,
|
|
1430
|
+
required=True,
|
|
1431
|
+
help="Cloud Server ID, can be multiple.",
|
|
1432
|
+
)
|
|
1433
|
+
@click.argument(
|
|
1434
|
+
"nat_mode",
|
|
1435
|
+
type=click.Choice(["dnat_and_snat", "snat", "no_nat"]),
|
|
1436
|
+
required=True,
|
|
1437
|
+
)
|
|
1438
|
+
def server_set_nat_mode(config, profile, verbose, nat_mode, server_ids):
|
|
1439
|
+
client = create_client(config, profile)
|
|
1440
|
+
|
|
1441
|
+
for server_id in server_ids:
|
|
1442
|
+
response = _server_set_nat_mode(client, server_id, nat_mode=nat_mode)
|
|
1443
|
+
if response.status_code == 204:
|
|
1444
|
+
click.echo(server_id)
|
|
1445
|
+
else:
|
|
1446
|
+
fmt.printer(response)
|
|
1447
|
+
|
|
1448
|
+
|
|
1449
|
+
# ------------------------------------------------------------- #
|
|
1450
|
+
# $ twc server ip #
|
|
1451
|
+
# ------------------------------------------------------------- #
|
|
1452
|
+
|
|
1453
|
+
|
|
1454
|
+
@server.group("ip", cls=ClickAliasedGroup)
|
|
1455
|
+
@options(GLOBAL_OPTIONS[:2])
|
|
1456
|
+
def ip_addr():
|
|
1457
|
+
"""Manage public IPs."""
|
|
1458
|
+
|
|
1459
|
+
|
|
1460
|
+
# ------------------------------------------------------------- #
|
|
1461
|
+
# $ twc server ip list #
|
|
1462
|
+
# ------------------------------------------------------------- #
|
|
1463
|
+
|
|
1464
|
+
|
|
1465
|
+
def print_ips(response: object):
|
|
1466
|
+
ips = response.json()["server_ips"]
|
|
1467
|
+
table = fmt.Table()
|
|
1468
|
+
table.header(["ADDRESS", "VERSION", "PTR", "PRIMARY"])
|
|
1469
|
+
for ip_addr in ips:
|
|
1470
|
+
table.row(
|
|
1471
|
+
[
|
|
1472
|
+
ip_addr["ip"],
|
|
1473
|
+
ip_addr["type"],
|
|
1474
|
+
ip_addr["ptr"],
|
|
1475
|
+
ip_addr["is_main"],
|
|
1476
|
+
]
|
|
1477
|
+
)
|
|
1478
|
+
table.print()
|
|
1479
|
+
|
|
1480
|
+
|
|
1481
|
+
@ip_addr.command(
|
|
1482
|
+
"list", aliases=["ls"], help="List public IPs attached to Cloud Server."
|
|
1483
|
+
)
|
|
1484
|
+
@options(GLOBAL_OPTIONS)
|
|
1485
|
+
@options(OUTPUT_FORMAT_OPTION)
|
|
1486
|
+
@click.argument("server_id", type=int, required=True)
|
|
1487
|
+
def server_ip_list(config, profile, verbose, output_format, server_id):
|
|
1488
|
+
client = create_client(config, profile)
|
|
1489
|
+
response = _server_ip_list(client, server_id)
|
|
1490
|
+
fmt.printer(response, output_format=output_format, func=print_ips)
|
|
1491
|
+
|
|
1492
|
+
|
|
1493
|
+
# ------------------------------------------------------------- #
|
|
1494
|
+
# $ twc server ip add #
|
|
1495
|
+
# ------------------------------------------------------------- #
|
|
1496
|
+
|
|
1497
|
+
|
|
1498
|
+
@ip_addr.command("add", help="Attach new IP to Cloud Server.")
|
|
1499
|
+
@options(GLOBAL_OPTIONS)
|
|
1500
|
+
@options(OUTPUT_FORMAT_OPTION)
|
|
1501
|
+
@click.option(
|
|
1502
|
+
"--ipv4/--ipv6", default=True, show_default=True, help="IP version."
|
|
1503
|
+
)
|
|
1504
|
+
@click.option("--ptr", help="IP address pointer (RDNS).")
|
|
1505
|
+
@click.option(
|
|
1506
|
+
"--to-server",
|
|
1507
|
+
"server_id",
|
|
1508
|
+
type=int,
|
|
1509
|
+
required=True,
|
|
1510
|
+
help="Cloud Server ID.",
|
|
1511
|
+
)
|
|
1512
|
+
def server_ip_add(
|
|
1513
|
+
config, profile, verbose, output_format, ipv4, ptr, server_id
|
|
1514
|
+
):
|
|
1515
|
+
client = create_client(config, profile)
|
|
1516
|
+
if not ipv4:
|
|
1517
|
+
log("Get Cloud Server location...")
|
|
1518
|
+
location = _server_get(client, server_id).json()["server"]["location"]
|
|
1519
|
+
if location not in REGIONS_WITH_IPV6:
|
|
1520
|
+
sys.exit(f"Error: IPv6 is not available in location '{location}'.")
|
|
1521
|
+
ip_version = "ipv6"
|
|
1522
|
+
else:
|
|
1523
|
+
ip_version = "ipv4"
|
|
1524
|
+
|
|
1525
|
+
response = _server_ip_add(client, server_id, version=ip_version, ptr=ptr)
|
|
1526
|
+
fmt.printer(
|
|
1527
|
+
response,
|
|
1528
|
+
output_format=output_format,
|
|
1529
|
+
func=lambda response: click.echo(response.json()["server_ip"]["ip"]),
|
|
1530
|
+
)
|
|
1531
|
+
|
|
1532
|
+
|
|
1533
|
+
# ------------------------------------------------------------- #
|
|
1534
|
+
# $ twc server ip remove #
|
|
1535
|
+
# ------------------------------------------------------------- #
|
|
1536
|
+
|
|
1537
|
+
|
|
1538
|
+
def get_server_id_by_ip(client, ip_address):
|
|
1539
|
+
"""Return server_id if IP address found or return None."""
|
|
1540
|
+
servers = _server_list(client, limit=10000).json()["servers"]
|
|
1541
|
+
for server in servers:
|
|
1542
|
+
for network in server["networks"]:
|
|
1543
|
+
for ip_addr in network["ips"]:
|
|
1544
|
+
if ip_address == ip_addr["ip"]:
|
|
1545
|
+
return server["id"]
|
|
1546
|
+
return None
|
|
1547
|
+
|
|
1548
|
+
|
|
1549
|
+
@ip_addr.command("remove", aliases=["rm"], help="Remove IP address.")
|
|
1550
|
+
@options(GLOBAL_OPTIONS)
|
|
1551
|
+
@click.confirmation_option(
|
|
1552
|
+
prompt="This action cannot be undone. Are you sure?"
|
|
1553
|
+
)
|
|
1554
|
+
@click.argument("ip_address", required=True)
|
|
1555
|
+
def server_ip_remove(config, profile, verbose, ip_address):
|
|
1556
|
+
client = create_client(config, profile)
|
|
1557
|
+
|
|
1558
|
+
log("Looking for IP address...")
|
|
1559
|
+
server_id = get_server_id_by_ip(client, ip_address)
|
|
1560
|
+
if not server_id:
|
|
1561
|
+
sys.exit(f"IP address '{ip_address}' not found.")
|
|
1562
|
+
|
|
1563
|
+
log("Check IP...")
|
|
1564
|
+
ips = _server_ip_list(client, server_id).json()["server_ips"]
|
|
1565
|
+
for ip_addr in ips:
|
|
1566
|
+
if ip_addr["ip"] == ip_address and ip_addr["is_main"]:
|
|
1567
|
+
sys.exit("Error: Cannot remove Cloud Server primaty IP address.")
|
|
1568
|
+
|
|
1569
|
+
response = _server_ip_remove(client, server_id, ip_address)
|
|
1570
|
+
|
|
1571
|
+
if response.status_code == 204:
|
|
1572
|
+
click.echo(ip_address)
|
|
1573
|
+
else:
|
|
1574
|
+
fmt.printer(response)
|
|
1575
|
+
|
|
1576
|
+
|
|
1577
|
+
# ------------------------------------------------------------- #
|
|
1578
|
+
# $ twc server ip set-ptr #
|
|
1579
|
+
# ------------------------------------------------------------- #
|
|
1580
|
+
|
|
1581
|
+
|
|
1582
|
+
@ip_addr.command("set-ptr", help="Set IP pointer (RDNS).")
|
|
1583
|
+
@options(GLOBAL_OPTIONS)
|
|
1584
|
+
@options(OUTPUT_FORMAT_OPTION)
|
|
1585
|
+
@click.option("--address", "ip_address", required=True, help="IP address.")
|
|
1586
|
+
@click.argument("ptr", required=True)
|
|
1587
|
+
def server_ip_set_ptr(
|
|
1588
|
+
config, profile, verbose, output_format, ip_address, ptr
|
|
1589
|
+
):
|
|
1590
|
+
client = create_client(config, profile)
|
|
1591
|
+
|
|
1592
|
+
log("Looking for IP address...")
|
|
1593
|
+
server_id = get_server_id_by_ip(client, ip_address)
|
|
1594
|
+
if not server_id:
|
|
1595
|
+
sys.exit(f"IP address '{ip_address}' not found.")
|
|
1596
|
+
|
|
1597
|
+
response = _server_ip_set_ptr(
|
|
1598
|
+
client, server_id, ip_addr=ip_address, ptr=ptr
|
|
1599
|
+
)
|
|
1600
|
+
fmt.printer(
|
|
1601
|
+
response,
|
|
1602
|
+
output_format=output_format,
|
|
1603
|
+
func=lambda response: click.echo(response.json()["server_ip"]["ip"]),
|
|
1604
|
+
)
|
|
1605
|
+
|
|
1606
|
+
|
|
1607
|
+
# ------------------------------------------------------------- #
|
|
1608
|
+
# $ twc server disk #
|
|
1609
|
+
# ------------------------------------------------------------- #
|
|
1610
|
+
|
|
1611
|
+
|
|
1612
|
+
@server.group("disk", cls=ClickAliasedGroup)
|
|
1613
|
+
@options(GLOBAL_OPTIONS[:2])
|
|
1614
|
+
def disk():
|
|
1615
|
+
"""Manage Cloud Server disks."""
|
|
1616
|
+
|
|
1617
|
+
|
|
1618
|
+
# ------------------------------------------------------------- #
|
|
1619
|
+
# $ twc server disk list #
|
|
1620
|
+
# ------------------------------------------------------------- #
|
|
1621
|
+
|
|
1622
|
+
|
|
1623
|
+
def print_disks(response: object):
|
|
1624
|
+
disks = response.json()["server_disks"]
|
|
1625
|
+
table = fmt.Table()
|
|
1626
|
+
table.header(
|
|
1627
|
+
[
|
|
1628
|
+
"ID",
|
|
1629
|
+
"NAME",
|
|
1630
|
+
"MOUNTED",
|
|
1631
|
+
"SYSTEM",
|
|
1632
|
+
"TYPE",
|
|
1633
|
+
"STATUS",
|
|
1634
|
+
"SIZE",
|
|
1635
|
+
"USED",
|
|
1636
|
+
]
|
|
1637
|
+
)
|
|
1638
|
+
for disk in disks:
|
|
1639
|
+
table.row(
|
|
1640
|
+
[
|
|
1641
|
+
disk["id"],
|
|
1642
|
+
disk["system_name"],
|
|
1643
|
+
disk["is_mounted"],
|
|
1644
|
+
disk["is_system"],
|
|
1645
|
+
disk["type"],
|
|
1646
|
+
disk["status"],
|
|
1647
|
+
str(round(disk["size"] / 1024)) + "G",
|
|
1648
|
+
str(round(disk["used"] / 1024, 1)) + "G",
|
|
1649
|
+
]
|
|
1650
|
+
)
|
|
1651
|
+
table.print()
|
|
1652
|
+
|
|
1653
|
+
|
|
1654
|
+
@disk.command("list", aliases=["ls"], help="List Cloud Server disks.")
|
|
1655
|
+
@options(GLOBAL_OPTIONS)
|
|
1656
|
+
@options(OUTPUT_FORMAT_OPTION)
|
|
1657
|
+
@click.argument("server_id", type=int, required=True)
|
|
1658
|
+
def server_disk_list(config, profile, verbose, output_format, server_id):
|
|
1659
|
+
client = create_client(config, profile)
|
|
1660
|
+
response = _server_disk_list(client, server_id)
|
|
1661
|
+
fmt.printer(response, output_format=output_format, func=print_disks)
|
|
1662
|
+
|
|
1663
|
+
|
|
1664
|
+
# ------------------------------------------------------------- #
|
|
1665
|
+
# $ twc server disk get #
|
|
1666
|
+
# ------------------------------------------------------------- #
|
|
1667
|
+
|
|
1668
|
+
|
|
1669
|
+
def print_disk(response: object):
|
|
1670
|
+
disk = response.json()["server_disk"]
|
|
1671
|
+
table = fmt.Table()
|
|
1672
|
+
table.header(
|
|
1673
|
+
[
|
|
1674
|
+
"ID",
|
|
1675
|
+
"NAME",
|
|
1676
|
+
"MOUNTED",
|
|
1677
|
+
"SYSTEM",
|
|
1678
|
+
"TYPE",
|
|
1679
|
+
"STATUS",
|
|
1680
|
+
"SIZE",
|
|
1681
|
+
"USED",
|
|
1682
|
+
]
|
|
1683
|
+
)
|
|
1684
|
+
table.row(
|
|
1685
|
+
[
|
|
1686
|
+
disk["id"],
|
|
1687
|
+
disk["system_name"],
|
|
1688
|
+
disk["is_mounted"],
|
|
1689
|
+
disk["is_system"],
|
|
1690
|
+
disk["type"],
|
|
1691
|
+
disk["status"],
|
|
1692
|
+
str(round(disk["size"] / 1024)) + "G",
|
|
1693
|
+
str(round(disk["used"] / 1024, 1)) + "G",
|
|
1694
|
+
]
|
|
1695
|
+
)
|
|
1696
|
+
table.print()
|
|
1697
|
+
|
|
1698
|
+
|
|
1699
|
+
def get_server_id_by_disk_id(client, disk_id: int) -> int:
|
|
1700
|
+
log("Looking for server_id by disk_id...")
|
|
1701
|
+
server_id = None
|
|
1702
|
+
servers = _server_list(client, limit=10000).json()["servers"]
|
|
1703
|
+
for server in servers:
|
|
1704
|
+
for disk in server["disks"]:
|
|
1705
|
+
if int(disk_id) == disk["id"]:
|
|
1706
|
+
server_id = server["id"]
|
|
1707
|
+
return server_id
|
|
1708
|
+
|
|
1709
|
+
|
|
1710
|
+
@disk.command("get", help="Get Cloud Server disk.")
|
|
1711
|
+
@options(GLOBAL_OPTIONS)
|
|
1712
|
+
@options(OUTPUT_FORMAT_OPTION)
|
|
1713
|
+
@click.argument("disk_id", type=int, required=True)
|
|
1714
|
+
def server_disk_get(config, profile, verbose, output_format, disk_id):
|
|
1715
|
+
client = create_client(config, profile)
|
|
1716
|
+
server_id = get_server_id_by_disk_id(client, disk_id)
|
|
1717
|
+
if not server_id:
|
|
1718
|
+
sys.exit(f"Error: Disk {disk_id} not found.")
|
|
1719
|
+
response = _server_disk_get(client, server_id, disk_id)
|
|
1720
|
+
fmt.printer(response, output_format=output_format, func=print_disk)
|
|
1721
|
+
|
|
1722
|
+
|
|
1723
|
+
# ------------------------------------------------------------- #
|
|
1724
|
+
# $ twc server disk add #
|
|
1725
|
+
# ------------------------------------------------------------- #
|
|
1726
|
+
|
|
1727
|
+
|
|
1728
|
+
@disk.command("add", help="Add disk to Cloud Server.")
|
|
1729
|
+
@options(GLOBAL_OPTIONS)
|
|
1730
|
+
@options(OUTPUT_FORMAT_OPTION)
|
|
1731
|
+
@click.option("--size", required=True, help="Disk size e.g. 50G.")
|
|
1732
|
+
@click.option(
|
|
1733
|
+
"--to-server",
|
|
1734
|
+
"server_id",
|
|
1735
|
+
type=int,
|
|
1736
|
+
metavar="SERVER_ID",
|
|
1737
|
+
required=True,
|
|
1738
|
+
help="Cloud Server ID.",
|
|
1739
|
+
)
|
|
1740
|
+
def server_disk_add(config, profile, verbose, output_format, size, server_id):
|
|
1741
|
+
client = create_client(config, profile)
|
|
1742
|
+
if check_value(size_to_mb(size), minv=5120, maxv=512000, step=5120):
|
|
1743
|
+
response = _server_disk_add(client, server_id, size=size_to_mb(size))
|
|
1744
|
+
else:
|
|
1745
|
+
raise click.BadParameter(
|
|
1746
|
+
"Value must be in range 5120-512000M with step 5120."
|
|
1747
|
+
)
|
|
1748
|
+
fmt.printer(
|
|
1749
|
+
response,
|
|
1750
|
+
output_format=output_format,
|
|
1751
|
+
func=lambda response: click.echo(response.json()["server_disk"]["id"]),
|
|
1752
|
+
)
|
|
1753
|
+
|
|
1754
|
+
|
|
1755
|
+
# ------------------------------------------------------------- #
|
|
1756
|
+
# $ twc server disk remove #
|
|
1757
|
+
# ------------------------------------------------------------- #
|
|
1758
|
+
|
|
1759
|
+
|
|
1760
|
+
@disk.command("remove", aliases=["rm"], help="Remove disks.")
|
|
1761
|
+
@options(GLOBAL_OPTIONS)
|
|
1762
|
+
@options(OUTPUT_FORMAT_OPTION)
|
|
1763
|
+
@click.confirmation_option(prompt="This action cannot be undone, continue?")
|
|
1764
|
+
@click.argument("disk_ids", nargs=-1, type=int, required=True)
|
|
1765
|
+
def server_disk_remove(config, profile, verbose, output_format, disk_ids):
|
|
1766
|
+
client = create_client(config, profile)
|
|
1767
|
+
for disk_id in disk_ids:
|
|
1768
|
+
server_id = get_server_id_by_disk_id(client, disk_id)
|
|
1769
|
+
response = _server_disk_remove(client, server_id, disk_id)
|
|
1770
|
+
if response.status_code == 204:
|
|
1771
|
+
click.echo(disk_id)
|
|
1772
|
+
else:
|
|
1773
|
+
fmt.printer(response)
|
|
1774
|
+
|
|
1775
|
+
|
|
1776
|
+
# ------------------------------------------------------------- #
|
|
1777
|
+
# $ twc server disk resize #
|
|
1778
|
+
# ------------------------------------------------------------- #
|
|
1779
|
+
|
|
1780
|
+
|
|
1781
|
+
@disk.command("resize", help="Change disk size (only increase).")
|
|
1782
|
+
@options(GLOBAL_OPTIONS)
|
|
1783
|
+
@options(OUTPUT_FORMAT_OPTION)
|
|
1784
|
+
@click.option("--size", required=True, help="Disk size e.g. 50G.")
|
|
1785
|
+
@click.argument("disk_id", type=int, required=True)
|
|
1786
|
+
def server_disk_resize(config, profile, verbose, output_format, size, disk_id):
|
|
1787
|
+
client = create_client(config, profile)
|
|
1788
|
+
server_id = get_server_id_by_disk_id(client, disk_id)
|
|
1789
|
+
if check_value(size_to_mb(size), minv=5120, maxv=512000, step=5120):
|
|
1790
|
+
response = _server_disk_resize(
|
|
1791
|
+
client, server_id, disk_id, size=size_to_mb(size)
|
|
1792
|
+
)
|
|
1793
|
+
else:
|
|
1794
|
+
raise click.BadParameter(
|
|
1795
|
+
"Value must be in range 5120-512000M with step 5120."
|
|
1796
|
+
)
|
|
1797
|
+
fmt.printer(
|
|
1798
|
+
response,
|
|
1799
|
+
output_format=output_format,
|
|
1800
|
+
func=lambda response: click.echo(response.json()["server_disk"]["id"]),
|
|
1801
|
+
)
|
|
1802
|
+
|
|
1803
|
+
|
|
1804
|
+
# ------------------------------------------------------------- #
|
|
1805
|
+
# $ twc server disk auto-backup #
|
|
1806
|
+
# ------------------------------------------------------------- #
|
|
1807
|
+
|
|
1808
|
+
|
|
1809
|
+
def print_autobackup_settings(response: object):
|
|
1810
|
+
table = fmt.Table()
|
|
1811
|
+
settings = response.json()["auto_backups_settings"]
|
|
1812
|
+
translated_keys = {
|
|
1813
|
+
"copy_count": "Keep copies",
|
|
1814
|
+
"creation_start_at": "Backup start date",
|
|
1815
|
+
"is_enabled": "Enabled",
|
|
1816
|
+
"interval": "Interval",
|
|
1817
|
+
"day_of_week": "Day of week",
|
|
1818
|
+
}
|
|
1819
|
+
for key in settings.keys():
|
|
1820
|
+
table.row([translated_keys[key], ":", settings[key]])
|
|
1821
|
+
table.print()
|
|
1822
|
+
|
|
1823
|
+
|
|
1824
|
+
@disk.command("auto-backup", help="Manage disk automatic backups settings.")
|
|
1825
|
+
@options(GLOBAL_OPTIONS)
|
|
1826
|
+
@options(OUTPUT_FORMAT_OPTION)
|
|
1827
|
+
@click.option(
|
|
1828
|
+
"--status", is_flag=True, help="Display automatic backups status."
|
|
1829
|
+
)
|
|
1830
|
+
@click.option(
|
|
1831
|
+
"--enable/--disable",
|
|
1832
|
+
default=False,
|
|
1833
|
+
show_default=True,
|
|
1834
|
+
help="Enable backups.",
|
|
1835
|
+
)
|
|
1836
|
+
@click.option(
|
|
1837
|
+
"--keep",
|
|
1838
|
+
type=int,
|
|
1839
|
+
default=1,
|
|
1840
|
+
show_default=True,
|
|
1841
|
+
help="Number of copies to keep.",
|
|
1842
|
+
)
|
|
1843
|
+
@click.option(
|
|
1844
|
+
"--start-date",
|
|
1845
|
+
type=click.DateTime(formats=["%Y-%m-%d"]),
|
|
1846
|
+
default=datetime.date.today().strftime("%Y-%m-%d"),
|
|
1847
|
+
help="Start date of the first backup creation. [default: today]",
|
|
1848
|
+
)
|
|
1849
|
+
@click.option(
|
|
1850
|
+
"--interval",
|
|
1851
|
+
type=click.Choice(["day", "week", "month"]),
|
|
1852
|
+
default="day",
|
|
1853
|
+
show_default=True,
|
|
1854
|
+
help="Backup interval.",
|
|
1855
|
+
)
|
|
1856
|
+
@click.option(
|
|
1857
|
+
"--day-of-week",
|
|
1858
|
+
type=click.IntRange(min=1, max=7),
|
|
1859
|
+
help="The day of the week on which backups will be created."
|
|
1860
|
+
" Works only with '--interval week'.",
|
|
1861
|
+
)
|
|
1862
|
+
@click.argument("disk_id", type=int, required=True)
|
|
1863
|
+
def server_disk_autobackup(
|
|
1864
|
+
config,
|
|
1865
|
+
profile,
|
|
1866
|
+
verbose,
|
|
1867
|
+
output_format,
|
|
1868
|
+
status,
|
|
1869
|
+
enable,
|
|
1870
|
+
keep,
|
|
1871
|
+
start_date,
|
|
1872
|
+
interval,
|
|
1873
|
+
day_of_week,
|
|
1874
|
+
disk_id,
|
|
1875
|
+
):
|
|
1876
|
+
client = create_client(config, profile)
|
|
1877
|
+
server_id = get_server_id_by_disk_id(client, disk_id)
|
|
1878
|
+
|
|
1879
|
+
if status:
|
|
1880
|
+
response = _server_disk_autobackup_status(client, server_id, disk_id)
|
|
1881
|
+
fmt.printer(
|
|
1882
|
+
response,
|
|
1883
|
+
output_format=output_format,
|
|
1884
|
+
func=print_autobackup_settings,
|
|
1885
|
+
)
|
|
1886
|
+
if response.json()["auto_backups_settings"]["is_enabled"]:
|
|
1887
|
+
sys.exit(0)
|
|
1888
|
+
else:
|
|
1889
|
+
sys.exit(1)
|
|
1890
|
+
|
|
1891
|
+
start_date = start_date.strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
1892
|
+
|
|
1893
|
+
response = _server_disk_autobackup_update(
|
|
1894
|
+
client,
|
|
1895
|
+
server_id,
|
|
1896
|
+
disk_id,
|
|
1897
|
+
is_enabled=enable,
|
|
1898
|
+
copy_count=keep,
|
|
1899
|
+
creation_start_at=start_date,
|
|
1900
|
+
interval=interval,
|
|
1901
|
+
day_of_week=day_of_week,
|
|
1902
|
+
)
|
|
1903
|
+
fmt.printer(
|
|
1904
|
+
response,
|
|
1905
|
+
output_format=output_format,
|
|
1906
|
+
func=print_autobackup_settings,
|
|
1907
|
+
)
|
|
1908
|
+
|
|
1909
|
+
|
|
1910
|
+
# ------------------------------------------------------------- #
|
|
1911
|
+
# $ twc server backup #
|
|
1912
|
+
# ------------------------------------------------------------- #
|
|
1913
|
+
|
|
1914
|
+
|
|
1915
|
+
@server.group("backup", cls=ClickAliasedGroup)
|
|
1916
|
+
@options(GLOBAL_OPTIONS[:2])
|
|
1917
|
+
def backup():
|
|
1918
|
+
"""Manage Cloud Server disk backups."""
|
|
1919
|
+
|
|
1920
|
+
|
|
1921
|
+
# ------------------------------------------------------------- #
|
|
1922
|
+
# $ twc server backup list #
|
|
1923
|
+
# ------------------------------------------------------------- #
|
|
1924
|
+
|
|
1925
|
+
|
|
1926
|
+
def print_backups(response: object):
|
|
1927
|
+
backups = response.json()["backups"]
|
|
1928
|
+
table = fmt.Table()
|
|
1929
|
+
table.header(
|
|
1930
|
+
[
|
|
1931
|
+
"ID",
|
|
1932
|
+
"DISK",
|
|
1933
|
+
"STATUS",
|
|
1934
|
+
"CREATED",
|
|
1935
|
+
"SIZE",
|
|
1936
|
+
"TYPE",
|
|
1937
|
+
"COMMENT",
|
|
1938
|
+
]
|
|
1939
|
+
)
|
|
1940
|
+
for backup in backups:
|
|
1941
|
+
table.row(
|
|
1942
|
+
[
|
|
1943
|
+
backup["id"],
|
|
1944
|
+
backup["name"],
|
|
1945
|
+
backup["status"],
|
|
1946
|
+
backup["created_at"],
|
|
1947
|
+
str(round(backup["size"] / 1024)) + "G",
|
|
1948
|
+
backup["type"],
|
|
1949
|
+
backup["comment"],
|
|
1950
|
+
]
|
|
1951
|
+
)
|
|
1952
|
+
table.print()
|
|
1953
|
+
|
|
1954
|
+
|
|
1955
|
+
@backup.command("list", aliases=["ls"], help="List backups by disk_id.")
|
|
1956
|
+
@options(GLOBAL_OPTIONS)
|
|
1957
|
+
@options(OUTPUT_FORMAT_OPTION)
|
|
1958
|
+
@click.argument("disk_id", type=int, required=True)
|
|
1959
|
+
def server_backup_list(config, profile, verbose, output_format, disk_id):
|
|
1960
|
+
client = create_client(config, profile)
|
|
1961
|
+
server_id = get_server_id_by_disk_id(client, disk_id)
|
|
1962
|
+
response = _server_backup_list(client, server_id, disk_id)
|
|
1963
|
+
fmt.printer(response, output_format=output_format, func=print_backups)
|
|
1964
|
+
|
|
1965
|
+
|
|
1966
|
+
# ------------------------------------------------------------- #
|
|
1967
|
+
# $ twc server backup get #
|
|
1968
|
+
# ------------------------------------------------------------- #
|
|
1969
|
+
|
|
1970
|
+
|
|
1971
|
+
def print_backup(response: object):
|
|
1972
|
+
backup = response.json()["backup"]
|
|
1973
|
+
table = fmt.Table()
|
|
1974
|
+
table.header(
|
|
1975
|
+
[
|
|
1976
|
+
"ID",
|
|
1977
|
+
"DISK",
|
|
1978
|
+
"STATUS",
|
|
1979
|
+
"CREATED",
|
|
1980
|
+
"SIZE",
|
|
1981
|
+
"TYPE",
|
|
1982
|
+
"COMMENT",
|
|
1983
|
+
]
|
|
1984
|
+
)
|
|
1985
|
+
table.row(
|
|
1986
|
+
[
|
|
1987
|
+
backup["id"],
|
|
1988
|
+
backup["name"],
|
|
1989
|
+
backup["status"],
|
|
1990
|
+
backup["created_at"],
|
|
1991
|
+
str(round(backup["size"] / 1024)) + "G",
|
|
1992
|
+
backup["type"],
|
|
1993
|
+
backup["comment"],
|
|
1994
|
+
]
|
|
1995
|
+
)
|
|
1996
|
+
table.print()
|
|
1997
|
+
|
|
1998
|
+
|
|
1999
|
+
@backup.command("get", help="Get backup.")
|
|
2000
|
+
@options(GLOBAL_OPTIONS)
|
|
2001
|
+
@options(OUTPUT_FORMAT_OPTION)
|
|
2002
|
+
@click.argument("disk_id", type=int, required=True)
|
|
2003
|
+
@click.argument("backup_id", type=int, required=True)
|
|
2004
|
+
def server_backup_get(
|
|
2005
|
+
config, profile, verbose, output_format, disk_id, backup_id
|
|
2006
|
+
):
|
|
2007
|
+
client = create_client(config, profile)
|
|
2008
|
+
server_id = get_server_id_by_disk_id(client, disk_id)
|
|
2009
|
+
response = _server_backup_get(client, server_id, disk_id, backup_id)
|
|
2010
|
+
fmt.printer(response, output_format=output_format, func=print_backup)
|
|
2011
|
+
|
|
2012
|
+
|
|
2013
|
+
# ------------------------------------------------------------- #
|
|
2014
|
+
# $ twc server backup create #
|
|
2015
|
+
# ------------------------------------------------------------- #
|
|
2016
|
+
|
|
2017
|
+
|
|
2018
|
+
@backup.command("create", help="Create disk backup.")
|
|
2019
|
+
@options(GLOBAL_OPTIONS)
|
|
2020
|
+
@options(OUTPUT_FORMAT_OPTION)
|
|
2021
|
+
@click.option("--comment", type=str, default=None, help="Comment.")
|
|
2022
|
+
@click.argument("disk_id", type=int, required=True)
|
|
2023
|
+
def server_backup_create(
|
|
2024
|
+
config, profile, verbose, output_format, comment, disk_id
|
|
2025
|
+
):
|
|
2026
|
+
client = create_client(config, profile)
|
|
2027
|
+
server_id = get_server_id_by_disk_id(client, disk_id)
|
|
2028
|
+
response = _server_backup_create(
|
|
2029
|
+
client, server_id, disk_id, comment=comment
|
|
2030
|
+
)
|
|
2031
|
+
fmt.printer(
|
|
2032
|
+
response,
|
|
2033
|
+
output_format=output_format,
|
|
2034
|
+
func=lambda response: click.echo(response.json()["backup"]["id"]),
|
|
2035
|
+
)
|
|
2036
|
+
|
|
2037
|
+
|
|
2038
|
+
# ------------------------------------------------------------- #
|
|
2039
|
+
# $ twc server backup set-property #
|
|
2040
|
+
# ------------------------------------------------------------- #
|
|
2041
|
+
|
|
2042
|
+
|
|
2043
|
+
@backup.command("set-property", help="Change backup properties.")
|
|
2044
|
+
@options(GLOBAL_OPTIONS)
|
|
2045
|
+
@options(OUTPUT_FORMAT_OPTION)
|
|
2046
|
+
@click.option("--comment", type=str, default=None, help="Comment.")
|
|
2047
|
+
@click.argument("disk_id", type=int, required=True)
|
|
2048
|
+
@click.argument("backup_id", type=int, required=True)
|
|
2049
|
+
def server_backup_set_property(
|
|
2050
|
+
config, profile, verbose, output_format, comment, disk_id, backup_id
|
|
2051
|
+
):
|
|
2052
|
+
client = create_client(config, profile)
|
|
2053
|
+
server_id = get_server_id_by_disk_id(client, disk_id)
|
|
2054
|
+
response = _server_backup_set_property(
|
|
2055
|
+
client, server_id, disk_id, backup_id, comment=comment
|
|
2056
|
+
)
|
|
2057
|
+
fmt.printer(
|
|
2058
|
+
response,
|
|
2059
|
+
output_format=output_format,
|
|
2060
|
+
func=lambda response: click.echo(response.json()["backup"]["id"]),
|
|
2061
|
+
)
|
|
2062
|
+
|
|
2063
|
+
|
|
2064
|
+
# ------------------------------------------------------------- #
|
|
2065
|
+
# $ twc server backup remove #
|
|
2066
|
+
# ------------------------------------------------------------- #
|
|
2067
|
+
|
|
2068
|
+
|
|
2069
|
+
@backup.command("remove", aliases=["rm"], help="Remove backup.")
|
|
2070
|
+
@options(GLOBAL_OPTIONS)
|
|
2071
|
+
@click.argument("disk_id", type=int, required=True)
|
|
2072
|
+
@click.argument("backup_id", nargs=-1, type=int, required=True)
|
|
2073
|
+
@click.confirmation_option(
|
|
2074
|
+
prompt="This action cannot be undone. Are you sure?"
|
|
2075
|
+
)
|
|
2076
|
+
def server_backup_remove(config, profile, verbose, disk_id, backup_id):
|
|
2077
|
+
client = create_client(config, profile)
|
|
2078
|
+
server_id = get_server_id_by_disk_id(client, disk_id)
|
|
2079
|
+
for backup in backup_id:
|
|
2080
|
+
response = _server_backup_remove(client, server_id, disk_id, backup)
|
|
2081
|
+
if response.status_code == 204:
|
|
2082
|
+
click.echo(server_id)
|
|
2083
|
+
else:
|
|
2084
|
+
fmt.printer(response)
|
|
2085
|
+
|
|
2086
|
+
|
|
2087
|
+
# ------------------------------------------------------------- #
|
|
2088
|
+
# $ twc server backup restore #
|
|
2089
|
+
# ------------------------------------------------------------- #
|
|
2090
|
+
|
|
2091
|
+
|
|
2092
|
+
@backup.command("restore", help="Restore backup.")
|
|
2093
|
+
@options(GLOBAL_OPTIONS)
|
|
2094
|
+
@click.argument("disk_id", type=int, required=True)
|
|
2095
|
+
@click.argument("backup_id", type=int, required=True)
|
|
2096
|
+
@click.confirmation_option(prompt="Data on target disk will lost. Continue?")
|
|
2097
|
+
def server_backup_restore(config, profile, verbose, disk_id, backup_id):
|
|
2098
|
+
client = create_client(config, profile)
|
|
2099
|
+
server_id = get_server_id_by_disk_id(client, disk_id)
|
|
2100
|
+
response = _server_backup_do_action(
|
|
2101
|
+
client, server_id, disk_id, backup_id, action="restore"
|
|
2102
|
+
)
|
|
2103
|
+
if response.status_code == 204:
|
|
2104
|
+
click.echo(server_id)
|
|
2105
|
+
else:
|
|
2106
|
+
fmt.printer(response)
|
|
2107
|
+
|
|
2108
|
+
|
|
2109
|
+
# ------------------------------------------------------------- #
|
|
2110
|
+
# $ twc server backup mount #
|
|
2111
|
+
# ------------------------------------------------------------- #
|
|
2112
|
+
|
|
2113
|
+
|
|
2114
|
+
@backup.command("mount", help="Attach backup as external drive.")
|
|
2115
|
+
@options(GLOBAL_OPTIONS)
|
|
2116
|
+
@click.argument("disk_id", type=int, required=True)
|
|
2117
|
+
@click.argument("backup_id", type=int, required=True)
|
|
2118
|
+
def server_backup_mount(config, profile, verbose, disk_id, backup_id):
|
|
2119
|
+
client = create_client(config, profile)
|
|
2120
|
+
server_id = get_server_id_by_disk_id(client, disk_id)
|
|
2121
|
+
response = _server_backup_do_action(
|
|
2122
|
+
client, server_id, disk_id, backup_id, action="mount"
|
|
2123
|
+
)
|
|
2124
|
+
if response.status_code == 204:
|
|
2125
|
+
click.echo(server_id)
|
|
2126
|
+
else:
|
|
2127
|
+
fmt.printer(response)
|
|
2128
|
+
|
|
2129
|
+
|
|
2130
|
+
# ------------------------------------------------------------- #
|
|
2131
|
+
# $ twc server backup unmount #
|
|
2132
|
+
# ------------------------------------------------------------- #
|
|
2133
|
+
|
|
2134
|
+
|
|
2135
|
+
@backup.command("unmount", help="Detach backup from Cloud Server.")
|
|
2136
|
+
@options(GLOBAL_OPTIONS)
|
|
2137
|
+
@click.argument("disk_id", type=int, required=True)
|
|
2138
|
+
@click.argument("backup_id", type=int, required=True)
|
|
2139
|
+
def server_backup_unmount(config, profile, verbose, disk_id, backup_id):
|
|
2140
|
+
client = create_client(config, profile)
|
|
2141
|
+
server_id = get_server_id_by_disk_id(client, disk_id)
|
|
2142
|
+
response = _server_backup_do_action(
|
|
2143
|
+
client, server_id, disk_id, backup_id, action="unmount"
|
|
2144
|
+
)
|
|
2145
|
+
if response.status_code == 204:
|
|
2146
|
+
click.echo(server_id)
|
|
2147
|
+
else:
|
|
2148
|
+
fmt.printer(response)
|