twc-cli 2.9.2__py3-none-any.whl → 2.10.0__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 +20 -0
- twc/__version__.py +1 -1
- twc/api/client.py +154 -5
- twc/api/types.py +6 -10
- twc/commands/database.py +528 -40
- twc/commands/server.py +16 -6
- twc/vars.py +11 -3
- {twc_cli-2.9.2.dist-info → twc_cli-2.10.0.dist-info}/METADATA +1 -1
- {twc_cli-2.9.2.dist-info → twc_cli-2.10.0.dist-info}/RECORD +12 -12
- {twc_cli-2.9.2.dist-info → twc_cli-2.10.0.dist-info}/COPYING +0 -0
- {twc_cli-2.9.2.dist-info → twc_cli-2.10.0.dist-info}/WHEEL +0 -0
- {twc_cli-2.9.2.dist-info → twc_cli-2.10.0.dist-info}/entry_points.txt +0 -0
CHANGELOG.md
CHANGED
|
@@ -2,6 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
В этом файле описаны все значимые изменения в Timeweb Cloud CLI. В выпусках мы придерживается правил [семантического версионирования](https://semver.org/lang/ru/).
|
|
4
4
|
|
|
5
|
+
# Версия 2.10.0 (2025.03.24)
|
|
6
|
+
|
|
7
|
+
## Добавлено
|
|
8
|
+
|
|
9
|
+
- Добавлена поддержка региона `de-1` (зона `fra-1`) для облачных серверов.
|
|
10
|
+
- Добавлены новые опции к команде `twc database create`:
|
|
11
|
+
- для создания пользователя СУБД: `--user-login`, `--user-password`, `--user-host`, `--user-privileges`, `--user-desc`;
|
|
12
|
+
- для создания первой базы данных: `--db-name`, `--db-desc`;
|
|
13
|
+
- для настроек сети в класетере СУБД: `--network-id`, `--private-ip`, `--public-ip`, `--no-public-ip`;
|
|
14
|
+
- для настройки автоматических бэкапов кластера: `--enable-backups`, `--backup-keep`, `--backup-start-date`, `--backup-interval`, `--backup-day-of-week`.
|
|
15
|
+
- Добавлена новая команда `twc database list-types` для вывода доступных к созданию управляемых баз данных.
|
|
16
|
+
- Добавлена новая команда `twc database backup schedule` позволяющая настроить параметры автоматического резервного копирования кластера.
|
|
17
|
+
- Добавлены новые команды для управления пользователями в кластерах СУБД: `twc database user list`, `twc database user get`, `twc database user create`, `twc database user remove`.
|
|
18
|
+
- Добавлены новые команды для управления базами данных в кластерах СУБД: `twc database instance list`, `twc database instance create`, `twc database instance remove`.
|
|
19
|
+
|
|
20
|
+
## Изменено
|
|
21
|
+
|
|
22
|
+
- Опция `--network` команды `twc server create` объявлена устаревшей и скрыта, добавлена эквивалентная опция `--network-id`.
|
|
23
|
+
- Опции `--login` и `--password` команды `twc database create` объявлены устаревшими и скрыты, вместо них теперь нужно использовать `--user-login` и `--user-password`.
|
|
24
|
+
|
|
5
25
|
# Версия 2.9.2 (2025.03.10)
|
|
6
26
|
|
|
7
27
|
## Исправлено
|
twc/__version__.py
CHANGED
twc/api/client.py
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"""Timeweb Cloud API client."""
|
|
2
2
|
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
3
5
|
from typing import Optional, Union, List
|
|
4
6
|
from uuid import UUID
|
|
5
7
|
from pathlib import Path
|
|
@@ -20,7 +22,6 @@ from .types import (
|
|
|
20
22
|
ServiceRegion,
|
|
21
23
|
ServiceAvailabilityZone,
|
|
22
24
|
ResourceType,
|
|
23
|
-
DBMS,
|
|
24
25
|
MySQLAuthPlugin,
|
|
25
26
|
LoadBalancerProto,
|
|
26
27
|
LoadBalancerAlgo,
|
|
@@ -727,19 +728,27 @@ class TimewebCloud(TimewebCloudBase):
|
|
|
727
728
|
"""Get database presets list."""
|
|
728
729
|
return self._request("GET", f"{self.api_url}/presets/dbs")
|
|
729
730
|
|
|
731
|
+
def get_database_types(self):
|
|
732
|
+
"""Get database types."""
|
|
733
|
+
return self._request("GET", f"{self.api_url}/database-types")
|
|
734
|
+
|
|
735
|
+
def get_database_configrators(self):
|
|
736
|
+
"""Get database_configurators."""
|
|
737
|
+
return self._request(
|
|
738
|
+
"GET", f"{self.api_url_v1}/configurator/databases"
|
|
739
|
+
)
|
|
740
|
+
|
|
730
741
|
def create_database(
|
|
731
742
|
self,
|
|
732
743
|
name: str,
|
|
733
|
-
dbms:
|
|
744
|
+
dbms: str,
|
|
734
745
|
preset_id: int,
|
|
735
746
|
password: str,
|
|
736
747
|
login: Optional[str] = None,
|
|
737
748
|
hash_type: Optional[MySQLAuthPlugin] = None,
|
|
738
749
|
config_parameters: Optional[dict] = None,
|
|
739
750
|
):
|
|
740
|
-
"""Create database."""
|
|
741
|
-
if dbms == "mysql8":
|
|
742
|
-
dbms = "mysql"
|
|
751
|
+
"""Create database. DEPRECATED."""
|
|
743
752
|
payload = {
|
|
744
753
|
"name": name,
|
|
745
754
|
"type": dbms,
|
|
@@ -751,6 +760,42 @@ class TimewebCloud(TimewebCloudBase):
|
|
|
751
760
|
}
|
|
752
761
|
return self._request("POST", f"{self.api_url}/dbs", json=payload)
|
|
753
762
|
|
|
763
|
+
def create_database2(
|
|
764
|
+
self,
|
|
765
|
+
name: str,
|
|
766
|
+
dbms: str,
|
|
767
|
+
network: Optional[dict[str, str]] = None,
|
|
768
|
+
preset_id: Optional[int] = None,
|
|
769
|
+
configurator_id: Optional[int] = None,
|
|
770
|
+
admin: Optional[dict[str, str]] = None,
|
|
771
|
+
instance: Optional[dict[str, str]] = None,
|
|
772
|
+
auto_backups: Optional[dict[str, str]] = None,
|
|
773
|
+
hash_type: Optional[MySQLAuthPlugin] = None,
|
|
774
|
+
config_parameters: Optional[dict] = None,
|
|
775
|
+
project_id: Optional[int] = None,
|
|
776
|
+
):
|
|
777
|
+
"""Create database cluster."""
|
|
778
|
+
payload = {
|
|
779
|
+
"name": name,
|
|
780
|
+
"type": dbms,
|
|
781
|
+
"hash_type": hash_type,
|
|
782
|
+
**({"network": network} if network else {}),
|
|
783
|
+
**(
|
|
784
|
+
{"config_parameters": config_parameters}
|
|
785
|
+
if config_parameters
|
|
786
|
+
else {}
|
|
787
|
+
),
|
|
788
|
+
**({"preset_id": preset_id} if preset_id else {}),
|
|
789
|
+
**(
|
|
790
|
+
{"configurator_id": configurator_id} if configurator_id else {}
|
|
791
|
+
),
|
|
792
|
+
**({"admin": admin} if admin else {}),
|
|
793
|
+
**({"auto_backups": auto_backups} if auto_backups else {}),
|
|
794
|
+
**({"instance": instance} if instance else {}),
|
|
795
|
+
**({"project_id": project_id} if project_id else {}),
|
|
796
|
+
}
|
|
797
|
+
return self._request("POST", f"{self.api_url}/databases", json=payload)
|
|
798
|
+
|
|
754
799
|
def update_database(
|
|
755
800
|
self,
|
|
756
801
|
db_id: int,
|
|
@@ -837,6 +882,110 @@ class TimewebCloud(TimewebCloudBase):
|
|
|
837
882
|
f"{self.api_url}/dbs/{db_id}/backups/{backup_id}",
|
|
838
883
|
)
|
|
839
884
|
|
|
885
|
+
def get_database_autobackup_settings(self, db_id: int):
|
|
886
|
+
"""Get database autobackup settings."""
|
|
887
|
+
return self._request(
|
|
888
|
+
"GET",
|
|
889
|
+
f"{self.api_url}/dbs/{db_id}/auto-backups",
|
|
890
|
+
)
|
|
891
|
+
|
|
892
|
+
def update_database_autobackup_settings(
|
|
893
|
+
self,
|
|
894
|
+
db_id: int,
|
|
895
|
+
is_enabled: Optional[bool] = None,
|
|
896
|
+
copy_count: Optional[int] = None,
|
|
897
|
+
creation_start_at: Optional[int] = None,
|
|
898
|
+
interval: Optional[BackupInterval] = None,
|
|
899
|
+
day_of_week: Optional[int] = None,
|
|
900
|
+
):
|
|
901
|
+
"""Set database autobackup settings."""
|
|
902
|
+
payload = {
|
|
903
|
+
**({"is_enabled": is_enabled} if is_enabled is not None else {}),
|
|
904
|
+
**({"copy_count": copy_count} if copy_count else {}),
|
|
905
|
+
**(
|
|
906
|
+
{"creation_start_at": creation_start_at}
|
|
907
|
+
if creation_start_at
|
|
908
|
+
else {}
|
|
909
|
+
),
|
|
910
|
+
**({"interval": interval} if interval else {}),
|
|
911
|
+
**({"day_of_week": day_of_week} if day_of_week else {}),
|
|
912
|
+
}
|
|
913
|
+
return self._request(
|
|
914
|
+
"PATCH",
|
|
915
|
+
f"{self.api_url}/dbs/{db_id}/auto-backups",
|
|
916
|
+
json=payload,
|
|
917
|
+
)
|
|
918
|
+
|
|
919
|
+
def create_database_user(
|
|
920
|
+
self,
|
|
921
|
+
db_id: int,
|
|
922
|
+
login: str,
|
|
923
|
+
password: str,
|
|
924
|
+
privileges: List[str],
|
|
925
|
+
host: Optional[str] = None,
|
|
926
|
+
instance_id: Optional[int] = None,
|
|
927
|
+
description: Optional[str] = None,
|
|
928
|
+
):
|
|
929
|
+
payload = {
|
|
930
|
+
"login": login,
|
|
931
|
+
"password": password,
|
|
932
|
+
"privileges": privileges,
|
|
933
|
+
}
|
|
934
|
+
if host:
|
|
935
|
+
payload["host"] = host
|
|
936
|
+
if instance_id:
|
|
937
|
+
payload["instance_id"] = instance_id
|
|
938
|
+
if description:
|
|
939
|
+
payload["description"] = description
|
|
940
|
+
return self._request(
|
|
941
|
+
"POST", f"{self.api_url}/databases/{db_id}/admins", json=payload
|
|
942
|
+
)
|
|
943
|
+
|
|
944
|
+
def get_database_users(self, db_id: int):
|
|
945
|
+
"""..."""
|
|
946
|
+
return self._request(
|
|
947
|
+
"GET", f"{self.api_url_v1}/databases/{db_id}/admins"
|
|
948
|
+
)
|
|
949
|
+
|
|
950
|
+
def get_database_user(self, db_id: int, user_id: int):
|
|
951
|
+
"""..."""
|
|
952
|
+
return self._request(
|
|
953
|
+
"GET", f"{self.api_url_v1}/databases/{db_id}/admins/{user_id}"
|
|
954
|
+
)
|
|
955
|
+
|
|
956
|
+
def delete_database_user(self, db_id: int, user_id: int):
|
|
957
|
+
"""..."""
|
|
958
|
+
return self._request(
|
|
959
|
+
"DELETE", f"{self.api_url_v1}/databases/{db_id}/admins/{user_id}"
|
|
960
|
+
)
|
|
961
|
+
|
|
962
|
+
def create_database_instance(
|
|
963
|
+
self,
|
|
964
|
+
db_id: int,
|
|
965
|
+
name: str,
|
|
966
|
+
description: Optional[str] = None,
|
|
967
|
+
):
|
|
968
|
+
"""..."""
|
|
969
|
+
payload = {"name": name}
|
|
970
|
+
if description:
|
|
971
|
+
payload["description"] = description
|
|
972
|
+
return self._request(
|
|
973
|
+
"POST", f"{self.api_url}/databases/{db_id}/instances"
|
|
974
|
+
)
|
|
975
|
+
|
|
976
|
+
def get_database_instances(self, db_id: int):
|
|
977
|
+
"""..."""
|
|
978
|
+
return self._request(
|
|
979
|
+
"GET", f"{self.api_url}/databases/{db_id}/instances"
|
|
980
|
+
)
|
|
981
|
+
|
|
982
|
+
def delete_database_instance(self, db_id: int, instance_id: int):
|
|
983
|
+
"""..."""
|
|
984
|
+
return self._request(
|
|
985
|
+
"DELETE",
|
|
986
|
+
f"{self.api_url}/databases/{db_id}/instances/{instance_id}",
|
|
987
|
+
)
|
|
988
|
+
|
|
840
989
|
# -----------------------------------------------------------------------
|
|
841
990
|
# Object Storage
|
|
842
991
|
|
twc/api/types.py
CHANGED
|
@@ -13,6 +13,7 @@ class ServiceRegion(str, Enum):
|
|
|
13
13
|
KZ_1 = "kz-1"
|
|
14
14
|
PL_1 = "pl-1"
|
|
15
15
|
NL_1 = "nl-1"
|
|
16
|
+
DE_1 = "de-1"
|
|
16
17
|
|
|
17
18
|
@classmethod
|
|
18
19
|
def get_zones(cls, region: str) -> List[str]:
|
|
@@ -29,6 +30,8 @@ class ServiceRegion(str, Enum):
|
|
|
29
30
|
return ["gdn-1"]
|
|
30
31
|
if region == cls.NL_1:
|
|
31
32
|
return ["ams-1"]
|
|
33
|
+
if region == cls.DE_1:
|
|
34
|
+
return ["fra-1"]
|
|
32
35
|
return []
|
|
33
36
|
|
|
34
37
|
|
|
@@ -44,6 +47,7 @@ class ServiceAvailabilityZone(str, Enum):
|
|
|
44
47
|
ALA_1 = "ala-1"
|
|
45
48
|
GDN_1 = "gdn-1"
|
|
46
49
|
AMS_1 = "ams-1"
|
|
50
|
+
FRA_1 = "fra-1"
|
|
47
51
|
|
|
48
52
|
@classmethod
|
|
49
53
|
def get_region(cls, zone: str) -> Optional[str]:
|
|
@@ -60,6 +64,8 @@ class ServiceAvailabilityZone(str, Enum):
|
|
|
60
64
|
return ServiceRegion.PL_1
|
|
61
65
|
if zone == cls.AMS_1:
|
|
62
66
|
return ServiceRegion.NL_1
|
|
67
|
+
if zone == cls.FRA_1:
|
|
68
|
+
return ServiceRegion.DE_1
|
|
63
69
|
return None
|
|
64
70
|
|
|
65
71
|
|
|
@@ -170,16 +176,6 @@ class ResourceType(str, Enum):
|
|
|
170
176
|
DEDICATED_SERVER = "dedicated"
|
|
171
177
|
|
|
172
178
|
|
|
173
|
-
class DBMS(str, Enum):
|
|
174
|
-
"""Available DBMS in Timeweb Cloud managed databases service."""
|
|
175
|
-
|
|
176
|
-
MYSQL_5 = "mysql5"
|
|
177
|
-
MYSQL_8 = "mysql8"
|
|
178
|
-
POSTGRES = "postgres"
|
|
179
|
-
REDIS = "redis"
|
|
180
|
-
MONGODB = "mongodb"
|
|
181
|
-
|
|
182
|
-
|
|
183
179
|
class MySQLAuthPlugin(str, Enum):
|
|
184
180
|
"""MySQL auth plugin options in Timeweb Cloud managed databases
|
|
185
181
|
service.
|
twc/commands/database.py
CHANGED
|
@@ -2,15 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
import re
|
|
4
4
|
import sys
|
|
5
|
+
import textwrap
|
|
6
|
+
from datetime import date, datetime
|
|
5
7
|
from typing import Optional, List
|
|
6
8
|
from pathlib import Path
|
|
9
|
+
from ipaddress import IPv4Address, IPv4Network
|
|
7
10
|
|
|
8
11
|
import typer
|
|
9
12
|
from requests import Response
|
|
10
13
|
|
|
11
14
|
from twc import fmt
|
|
12
15
|
from twc.typerx import TyperAlias
|
|
13
|
-
from twc.api import ServiceRegion,
|
|
16
|
+
from twc.api import ServiceRegion, MySQLAuthPlugin, BackupInterval
|
|
14
17
|
from twc.apiwrap import create_client
|
|
15
18
|
from twc.utils import merge_dicts
|
|
16
19
|
from .common import (
|
|
@@ -27,6 +30,12 @@ from .common import (
|
|
|
27
30
|
database = TyperAlias(help=__doc__)
|
|
28
31
|
database_backup = TyperAlias(help="Manage database backups.")
|
|
29
32
|
database.add_typer(database_backup, name="backup")
|
|
33
|
+
database_user = TyperAlias(help="Manage database users.")
|
|
34
|
+
database.add_typer(database_user, name="user")
|
|
35
|
+
database_instance = TyperAlias(
|
|
36
|
+
help="Manage instances in cluster (databases/topics/etc)."
|
|
37
|
+
)
|
|
38
|
+
database.add_typer(database_instance, name="instance", aliases=["db"])
|
|
30
39
|
|
|
31
40
|
|
|
32
41
|
# ------------------------------------------------------------- #
|
|
@@ -206,6 +215,34 @@ def database_list_presets(
|
|
|
206
215
|
)
|
|
207
216
|
|
|
208
217
|
|
|
218
|
+
# ------------------------------------------------------------- #
|
|
219
|
+
# $ twc database list-types #
|
|
220
|
+
# ------------------------------------------------------------- #
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
@database.command("list-types", "lt")
|
|
224
|
+
def database_list_types(
|
|
225
|
+
verbose: Optional[bool] = verbose_option,
|
|
226
|
+
config: Optional[Path] = config_option,
|
|
227
|
+
profile: Optional[str] = profile_option,
|
|
228
|
+
output_format: Optional[str] = output_format_option,
|
|
229
|
+
):
|
|
230
|
+
"""List database configuration presets."""
|
|
231
|
+
client = create_client(config, profile)
|
|
232
|
+
response = client.get_database_types().json()
|
|
233
|
+
table = fmt.Table()
|
|
234
|
+
table.header(["TYPE", "DATABASE", "VERSION"])
|
|
235
|
+
for service in response["types"]:
|
|
236
|
+
table.row(
|
|
237
|
+
[
|
|
238
|
+
service["type"],
|
|
239
|
+
service["name"],
|
|
240
|
+
service["version"],
|
|
241
|
+
]
|
|
242
|
+
)
|
|
243
|
+
table.print()
|
|
244
|
+
|
|
245
|
+
|
|
209
246
|
# ------------------------------------------------------------- #
|
|
210
247
|
# $ twc database create #
|
|
211
248
|
# ------------------------------------------------------------- #
|
|
@@ -228,6 +265,12 @@ def set_params(params: list) -> dict:
|
|
|
228
265
|
return parameters
|
|
229
266
|
|
|
230
267
|
|
|
268
|
+
def dbms_parameters_callback(value: str) -> List[str]:
|
|
269
|
+
if value:
|
|
270
|
+
return value.split(",")
|
|
271
|
+
return []
|
|
272
|
+
|
|
273
|
+
|
|
231
274
|
@database.command("create")
|
|
232
275
|
def database_create(
|
|
233
276
|
verbose: Optional[bool] = verbose_option,
|
|
@@ -235,80 +278,185 @@ def database_create(
|
|
|
235
278
|
profile: Optional[str] = profile_option,
|
|
236
279
|
output_format: Optional[str] = output_format_option,
|
|
237
280
|
preset_id: int = typer.Option(..., help="Database configuration preset."),
|
|
238
|
-
dbms:
|
|
281
|
+
dbms: str = typer.Option(
|
|
239
282
|
...,
|
|
240
283
|
"--type",
|
|
241
|
-
|
|
242
|
-
help="Database management system.",
|
|
284
|
+
help="Database management system. See TYPE in `twc database list-types`.",
|
|
243
285
|
),
|
|
244
286
|
hash_type: Optional[MySQLAuthPlugin] = typer.Option(
|
|
245
287
|
MySQLAuthPlugin.CACHING_SHA2.value,
|
|
246
288
|
case_sensitive=False,
|
|
247
289
|
help="Authentication plugin for MySQL.",
|
|
248
290
|
),
|
|
249
|
-
name: str = typer.Option(..., help="Database
|
|
291
|
+
name: str = typer.Option(..., help="Database cluster display name."),
|
|
250
292
|
params: Optional[List[str]] = typer.Option(
|
|
251
293
|
None,
|
|
252
294
|
"--param",
|
|
253
295
|
metavar="PARAM=VALUE",
|
|
254
|
-
help="Database parameters, can be multiple.",
|
|
296
|
+
help="Database config parameters, can be multiple.",
|
|
255
297
|
),
|
|
256
|
-
login: Optional[str] = typer.Option(
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
298
|
+
login: Optional[str] = typer.Option(
|
|
299
|
+
# DEPRECATED
|
|
300
|
+
None,
|
|
301
|
+
hidden=True,
|
|
302
|
+
help="Database user login.",
|
|
303
|
+
),
|
|
304
|
+
password: Optional[str] = typer.Option(
|
|
305
|
+
# DEPRECATED
|
|
306
|
+
None,
|
|
307
|
+
hidden=True,
|
|
308
|
+
),
|
|
309
|
+
user_login: Optional[str] = typer.Option(None, help="User login."),
|
|
310
|
+
user_password: Optional[str] = typer.Option(None, help="User password."),
|
|
311
|
+
user_host: Optional[str] = typer.Option(
|
|
312
|
+
"%", help="User host for MySQL, Postgres"
|
|
313
|
+
),
|
|
314
|
+
user_privileges: Optional[str] = typer.Option(
|
|
315
|
+
[],
|
|
316
|
+
help="Comma-separated list of user privileges.",
|
|
317
|
+
callback=dbms_parameters_callback,
|
|
318
|
+
),
|
|
319
|
+
user_desc: Optional[str] = typer.Option(None, help="Comment for user."),
|
|
320
|
+
db_name: Optional[str] = typer.Option(None, help="Database name."),
|
|
321
|
+
db_desc: Optional[str] = typer.Option(None, help="Database comment."),
|
|
322
|
+
network_id: Optional[str] = typer.Option(None, help="Private network ID."),
|
|
323
|
+
private_ip: Optional[str] = typer.Option(
|
|
324
|
+
None, help="Private IPv4 address."
|
|
325
|
+
),
|
|
326
|
+
public_ip: Optional[str] = typer.Option(
|
|
327
|
+
None, help="Public IPv4 address. New address by default."
|
|
328
|
+
),
|
|
329
|
+
no_public_ip: Optional[bool] = typer.Option(
|
|
330
|
+
False, "--no-public-ip", help="Do not add public IPv4 address."
|
|
262
331
|
),
|
|
263
332
|
project_id: Optional[int] = typer.Option(
|
|
264
333
|
None,
|
|
265
334
|
envvar="TWC_PROJECT",
|
|
266
335
|
show_envvar=False,
|
|
267
336
|
callback=load_from_config_callback,
|
|
268
|
-
help="Add database to specific project.",
|
|
337
|
+
help="Add database cluster to specific project.",
|
|
338
|
+
),
|
|
339
|
+
enable_backups: Optional[bool] = typer.Option(
|
|
340
|
+
False,
|
|
341
|
+
"--enable-backups",
|
|
342
|
+
help="Enable atomatic backups of database cluster.",
|
|
343
|
+
),
|
|
344
|
+
backup_keep: Optional[int] = typer.Option(
|
|
345
|
+
1,
|
|
346
|
+
show_default=True,
|
|
347
|
+
help="Number of backups to keep.",
|
|
348
|
+
),
|
|
349
|
+
backup_start_date: Optional[datetime] = typer.Option(
|
|
350
|
+
date.today().strftime("%Y-%m-%d"),
|
|
351
|
+
formats=["%Y-%m-%d"],
|
|
352
|
+
show_default=False,
|
|
353
|
+
help="Start date of the first backup creation [default: today].",
|
|
354
|
+
),
|
|
355
|
+
backup_interval: Optional[BackupInterval] = typer.Option(
|
|
356
|
+
BackupInterval.DAY.value,
|
|
357
|
+
"--backup-interval",
|
|
358
|
+
help="Backup interval.",
|
|
359
|
+
),
|
|
360
|
+
backup_day_of_week: Optional[int] = typer.Option(
|
|
361
|
+
1,
|
|
362
|
+
min=1,
|
|
363
|
+
max=7,
|
|
364
|
+
help="The day of the week on which backups will be created."
|
|
365
|
+
" NOTE: This option works only with interval 'week'."
|
|
366
|
+
" First day of week is monday.",
|
|
269
367
|
),
|
|
270
368
|
):
|
|
271
|
-
"""Create managed database
|
|
369
|
+
"""Create managed database cluster."""
|
|
272
370
|
client = create_client(config, profile)
|
|
273
371
|
|
|
274
|
-
# Check preset_id
|
|
275
|
-
for preset in client.get_database_presets().json()["databases_presets"]:
|
|
276
|
-
if preset["id"] == preset_id:
|
|
277
|
-
_dbms = re.sub(
|
|
278
|
-
r"[0-9]+", "", dbms
|
|
279
|
-
) # transform 'mysql5' to 'mysql', etc.
|
|
280
|
-
if not preset["type"].startswith(_dbms):
|
|
281
|
-
sys.exit(
|
|
282
|
-
f"Error: DBMS '{dbms}' doesn't match with preset_id type."
|
|
283
|
-
)
|
|
284
|
-
|
|
285
372
|
payload = {
|
|
286
373
|
"dbms": dbms,
|
|
287
374
|
"preset_id": preset_id,
|
|
288
375
|
"name": name,
|
|
289
376
|
"hash_type": hash_type,
|
|
290
|
-
"login": login,
|
|
291
|
-
"password": password,
|
|
292
377
|
"config_parameters": {},
|
|
378
|
+
"network": {},
|
|
293
379
|
}
|
|
294
380
|
|
|
381
|
+
if enable_backups:
|
|
382
|
+
backup_start_date = backup_start_date.strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
383
|
+
payload["auto_backups"] = {
|
|
384
|
+
"copy_count": backup_keep,
|
|
385
|
+
"creation_started_at": backup_start_date,
|
|
386
|
+
"interval": backup_interval,
|
|
387
|
+
"day_of_week": backup_day_of_week,
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if network_id:
|
|
391
|
+
payload["network"]["id"] = network_id
|
|
392
|
+
if private_ip:
|
|
393
|
+
net = IPv4Network(
|
|
394
|
+
client.get_vpc(network_id).json()["vpc"]["subnet_v4"]
|
|
395
|
+
)
|
|
396
|
+
if IPv4Address(private_ip) >= net.network_address + 4:
|
|
397
|
+
payload["network"]["ip"] = private_ip
|
|
398
|
+
else:
|
|
399
|
+
# First 3 addresses is reserved by Timeweb Cloud for gateway and future use.
|
|
400
|
+
sys.exit(
|
|
401
|
+
f"Error: Private address '{private_ip}' is not allowed. "
|
|
402
|
+
"IP must be at least the fourth in order in the network."
|
|
403
|
+
)
|
|
404
|
+
if public_ip:
|
|
405
|
+
try:
|
|
406
|
+
_ = IPv4Address(public_ip)
|
|
407
|
+
payload["network"]["floating_ip"] = public_ip
|
|
408
|
+
except ValueError:
|
|
409
|
+
sys.exit(f"Error: '{public_ip}' is not valid IPv4 address.")
|
|
410
|
+
else:
|
|
411
|
+
# New public IPv4 address will be automatically requested with
|
|
412
|
+
# correct availability zone. This is an official dirty hack.
|
|
413
|
+
if no_public_ip is False:
|
|
414
|
+
payload["network"]["floating_ip"] = "create_ip"
|
|
415
|
+
|
|
416
|
+
if login:
|
|
417
|
+
print(
|
|
418
|
+
"--login is deprecated use --user-login instead", file=sys.stderr
|
|
419
|
+
)
|
|
420
|
+
user_login = login
|
|
421
|
+
|
|
422
|
+
if password:
|
|
423
|
+
print(
|
|
424
|
+
"--password is deprecated use --user-password instead",
|
|
425
|
+
file=sys.stderr,
|
|
426
|
+
)
|
|
427
|
+
user_password = password
|
|
428
|
+
|
|
429
|
+
if user_password and not user_login:
|
|
430
|
+
sys.exit("Error: --user-login required if --user-password is set.")
|
|
431
|
+
|
|
432
|
+
if user_login:
|
|
433
|
+
if not user_password:
|
|
434
|
+
user_password = typer.prompt(
|
|
435
|
+
"Database user password",
|
|
436
|
+
hide_input=True,
|
|
437
|
+
confirmation_prompt=True,
|
|
438
|
+
)
|
|
439
|
+
payload["admin"] = {
|
|
440
|
+
"login": user_login,
|
|
441
|
+
"password": user_password,
|
|
442
|
+
"host": user_host,
|
|
443
|
+
"privileges": user_privileges,
|
|
444
|
+
"description": user_desc or "",
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
if db_name:
|
|
448
|
+
payload["instance"] = {
|
|
449
|
+
"name": db_name,
|
|
450
|
+
"description": db_desc or "",
|
|
451
|
+
}
|
|
452
|
+
|
|
295
453
|
if params:
|
|
296
454
|
payload["config_parameters"] = set_params(params)
|
|
297
455
|
|
|
298
456
|
if project_id:
|
|
299
|
-
|
|
300
|
-
prj["id"] for prj in client.get_projects().json()["projects"]
|
|
301
|
-
]:
|
|
302
|
-
sys.exit(f"Wrong project ID: Project '{project_id}' not found.")
|
|
457
|
+
payload["project_id"] = project_id
|
|
303
458
|
|
|
304
|
-
response = client.
|
|
305
|
-
|
|
306
|
-
# Add created DB to project if set
|
|
307
|
-
if project_id:
|
|
308
|
-
client.add_database_to_project(
|
|
309
|
-
response.json()["db"]["id"],
|
|
310
|
-
project_id,
|
|
311
|
-
)
|
|
459
|
+
response = client.create_database2(**payload)
|
|
312
460
|
|
|
313
461
|
fmt.printer(
|
|
314
462
|
response,
|
|
@@ -537,4 +685,344 @@ def database_backup_restore(
|
|
|
537
685
|
# $ twc database backup schedule #
|
|
538
686
|
# ------------------------------------------------------------- #
|
|
539
687
|
|
|
540
|
-
|
|
688
|
+
|
|
689
|
+
def print_autobackup_settings(response: Response):
|
|
690
|
+
"""Print backup settings info."""
|
|
691
|
+
table = fmt.Table()
|
|
692
|
+
settings = response.json()["auto_backups_settings"]
|
|
693
|
+
translated_keys = {
|
|
694
|
+
"copy_count": "Keep copies",
|
|
695
|
+
"creation_start_at": "Backup start date",
|
|
696
|
+
"is_enabled": "Enabled",
|
|
697
|
+
"interval": "Interval",
|
|
698
|
+
"day_of_week": "Day of week",
|
|
699
|
+
}
|
|
700
|
+
for key in settings.keys():
|
|
701
|
+
table.row([translated_keys[key], ":", settings[key]])
|
|
702
|
+
table.print()
|
|
703
|
+
|
|
704
|
+
|
|
705
|
+
@database_backup.command("schedule")
|
|
706
|
+
def database_backup_schedule(
|
|
707
|
+
db_id: int,
|
|
708
|
+
verbose: Optional[bool] = verbose_option,
|
|
709
|
+
config: Optional[Path] = config_option,
|
|
710
|
+
profile: Optional[str] = profile_option,
|
|
711
|
+
output_format: Optional[str] = output_format_option,
|
|
712
|
+
status: bool = typer.Option(
|
|
713
|
+
False,
|
|
714
|
+
"--status",
|
|
715
|
+
help="Display automatic backups status.",
|
|
716
|
+
),
|
|
717
|
+
enable: Optional[bool] = typer.Option(
|
|
718
|
+
None,
|
|
719
|
+
"--enable/--disable",
|
|
720
|
+
show_default=False,
|
|
721
|
+
help="Enable or disable automatic backups.",
|
|
722
|
+
),
|
|
723
|
+
keep: int = typer.Option(
|
|
724
|
+
1,
|
|
725
|
+
show_default=True,
|
|
726
|
+
help="Number of backups to keep.",
|
|
727
|
+
),
|
|
728
|
+
start_date: datetime = typer.Option(
|
|
729
|
+
date.today().strftime("%Y-%m-%d"),
|
|
730
|
+
formats=["%Y-%m-%d"],
|
|
731
|
+
show_default=False,
|
|
732
|
+
help="Start date of the first backup creation [default: today].",
|
|
733
|
+
),
|
|
734
|
+
interval: BackupInterval = typer.Option(
|
|
735
|
+
BackupInterval.DAY.value,
|
|
736
|
+
"--interval",
|
|
737
|
+
help="Backup interval.",
|
|
738
|
+
),
|
|
739
|
+
day_of_week: Optional[int] = typer.Option(
|
|
740
|
+
1,
|
|
741
|
+
min=1,
|
|
742
|
+
max=7,
|
|
743
|
+
help="The day of the week on which backups will be created."
|
|
744
|
+
" NOTE: This option works only with interval 'week'."
|
|
745
|
+
" First day of week is monday.",
|
|
746
|
+
),
|
|
747
|
+
):
|
|
748
|
+
"""Manage database cluster automatic backup settings."""
|
|
749
|
+
client = create_client(config, profile)
|
|
750
|
+
|
|
751
|
+
if status:
|
|
752
|
+
response = client.get_database_autobackup_settings(db_id)
|
|
753
|
+
fmt.printer(
|
|
754
|
+
response,
|
|
755
|
+
output_format=output_format,
|
|
756
|
+
func=print_autobackup_settings,
|
|
757
|
+
)
|
|
758
|
+
if response.json()["auto_backups_settings"]["is_enabled"]:
|
|
759
|
+
sys.exit(0)
|
|
760
|
+
else:
|
|
761
|
+
sys.exit(1)
|
|
762
|
+
|
|
763
|
+
start_date = start_date.strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
764
|
+
|
|
765
|
+
response = client.update_database_autobackup_settings(
|
|
766
|
+
db_id,
|
|
767
|
+
is_enabled=enable,
|
|
768
|
+
copy_count=keep,
|
|
769
|
+
creation_start_at=start_date,
|
|
770
|
+
interval=interval,
|
|
771
|
+
day_of_week=day_of_week,
|
|
772
|
+
)
|
|
773
|
+
fmt.printer(
|
|
774
|
+
response,
|
|
775
|
+
output_format=output_format,
|
|
776
|
+
func=print_autobackup_settings,
|
|
777
|
+
)
|
|
778
|
+
|
|
779
|
+
|
|
780
|
+
# ------------------------------------------------------------- #
|
|
781
|
+
# $ twc database user list #
|
|
782
|
+
# ------------------------------------------------------------- #
|
|
783
|
+
|
|
784
|
+
|
|
785
|
+
def _print_database_users(response: Response):
|
|
786
|
+
users = response.json()["admins"]
|
|
787
|
+
table = fmt.Table()
|
|
788
|
+
table.header(["ID", "LOGIN", "HOST", "CREATED"])
|
|
789
|
+
for user in users:
|
|
790
|
+
table.row(
|
|
791
|
+
[
|
|
792
|
+
user["id"],
|
|
793
|
+
user["login"],
|
|
794
|
+
user["host"],
|
|
795
|
+
user["created_at"],
|
|
796
|
+
]
|
|
797
|
+
)
|
|
798
|
+
table.print()
|
|
799
|
+
|
|
800
|
+
|
|
801
|
+
@database_user.command("list", "ls")
|
|
802
|
+
def database_user_list(
|
|
803
|
+
db_id: int,
|
|
804
|
+
verbose: Optional[bool] = verbose_option,
|
|
805
|
+
config: Optional[Path] = config_option,
|
|
806
|
+
profile: Optional[str] = profile_option,
|
|
807
|
+
output_format: Optional[str] = output_format_option,
|
|
808
|
+
):
|
|
809
|
+
"""List database users."""
|
|
810
|
+
client = create_client(config, profile)
|
|
811
|
+
response = client.get_database_users(db_id)
|
|
812
|
+
fmt.printer(
|
|
813
|
+
response,
|
|
814
|
+
output_format=output_format,
|
|
815
|
+
func=_print_database_users,
|
|
816
|
+
)
|
|
817
|
+
|
|
818
|
+
|
|
819
|
+
# ------------------------------------------------------------- #
|
|
820
|
+
# $ twc database user get #
|
|
821
|
+
# ------------------------------------------------------------- #
|
|
822
|
+
|
|
823
|
+
|
|
824
|
+
def _print_database_user(response: Response):
|
|
825
|
+
user = response.json()["admin"]
|
|
826
|
+
out = textwrap.dedent(
|
|
827
|
+
f"""
|
|
828
|
+
Login: {user['login']}
|
|
829
|
+
Host: {user['host']}
|
|
830
|
+
Created: {user['created_at']}
|
|
831
|
+
Description: {user['description']}
|
|
832
|
+
"""
|
|
833
|
+
).strip()
|
|
834
|
+
print(out)
|
|
835
|
+
print()
|
|
836
|
+
table = fmt.Table()
|
|
837
|
+
table.header(["INSTANCE_ID", "PRIVILEGES"])
|
|
838
|
+
for instance in user["instances"]:
|
|
839
|
+
table.row(
|
|
840
|
+
[
|
|
841
|
+
instance["instance_id"],
|
|
842
|
+
", ".join(instance["privileges"]),
|
|
843
|
+
]
|
|
844
|
+
)
|
|
845
|
+
table.print()
|
|
846
|
+
|
|
847
|
+
|
|
848
|
+
@database_user.command("get")
|
|
849
|
+
def database_user_get(
|
|
850
|
+
db_id: int,
|
|
851
|
+
user_id: int,
|
|
852
|
+
verbose: Optional[bool] = verbose_option,
|
|
853
|
+
config: Optional[Path] = config_option,
|
|
854
|
+
profile: Optional[str] = profile_option,
|
|
855
|
+
output_format: Optional[str] = output_format_option,
|
|
856
|
+
):
|
|
857
|
+
"""Get database user."""
|
|
858
|
+
client = create_client(config, profile)
|
|
859
|
+
response = client.get_database_user(db_id, user_id)
|
|
860
|
+
fmt.printer(
|
|
861
|
+
response,
|
|
862
|
+
output_format=output_format,
|
|
863
|
+
func=_print_database_user,
|
|
864
|
+
)
|
|
865
|
+
|
|
866
|
+
|
|
867
|
+
# ------------------------------------------------------------- #
|
|
868
|
+
# $ twc database user create #
|
|
869
|
+
# ------------------------------------------------------------- #
|
|
870
|
+
|
|
871
|
+
|
|
872
|
+
@database_user.command("create")
|
|
873
|
+
def database_user_create(
|
|
874
|
+
db_id: int,
|
|
875
|
+
verbose: Optional[bool] = verbose_option,
|
|
876
|
+
config: Optional[Path] = config_option,
|
|
877
|
+
profile: Optional[str] = profile_option,
|
|
878
|
+
output_format: Optional[str] = output_format_option,
|
|
879
|
+
login: str = typer.Option(..., help="User login."),
|
|
880
|
+
password: str = typer.Option(
|
|
881
|
+
...,
|
|
882
|
+
prompt=True,
|
|
883
|
+
hide_input=True,
|
|
884
|
+
confirmation_prompt=True,
|
|
885
|
+
help="User password.",
|
|
886
|
+
),
|
|
887
|
+
host: Optional[str] = typer.Option(
|
|
888
|
+
"%", help="User host for MySQL, Postgres"
|
|
889
|
+
),
|
|
890
|
+
instance_id: Optional[int] = typer.Option(
|
|
891
|
+
None,
|
|
892
|
+
help="The specific instance ID to which the privileges will be "
|
|
893
|
+
"applied. If not specified, the privileges will be applied to "
|
|
894
|
+
"all available instances.",
|
|
895
|
+
),
|
|
896
|
+
privileges: Optional[str] = typer.Option(
|
|
897
|
+
[],
|
|
898
|
+
help="Comma-separated list of user privileges.",
|
|
899
|
+
callback=dbms_parameters_callback,
|
|
900
|
+
),
|
|
901
|
+
desc: Optional[str] = typer.Option(None, help="Comment for user."),
|
|
902
|
+
):
|
|
903
|
+
"""Create database users."""
|
|
904
|
+
client = create_client(config, profile)
|
|
905
|
+
response = client.create_database_user(
|
|
906
|
+
db_id=db_id,
|
|
907
|
+
login=login,
|
|
908
|
+
password=password,
|
|
909
|
+
privileges=privileges,
|
|
910
|
+
host=host,
|
|
911
|
+
instance_id=instance_id,
|
|
912
|
+
description=desc,
|
|
913
|
+
)
|
|
914
|
+
fmt.printer(
|
|
915
|
+
response,
|
|
916
|
+
output_format=output_format,
|
|
917
|
+
func=lambda response: print(response.json()["admin"]["id"]),
|
|
918
|
+
)
|
|
919
|
+
|
|
920
|
+
|
|
921
|
+
# ------------------------------------------------------------- #
|
|
922
|
+
# $ twc database user remove #
|
|
923
|
+
# ------------------------------------------------------------- #
|
|
924
|
+
|
|
925
|
+
|
|
926
|
+
@database_user.command("remove", "rm")
|
|
927
|
+
def database_user_remove(
|
|
928
|
+
db_id: int,
|
|
929
|
+
user_id: int,
|
|
930
|
+
verbose: Optional[bool] = verbose_option,
|
|
931
|
+
config: Optional[Path] = config_option,
|
|
932
|
+
profile: Optional[str] = profile_option,
|
|
933
|
+
):
|
|
934
|
+
"""Delete database user."""
|
|
935
|
+
client = create_client(config, profile)
|
|
936
|
+
response = client.get_database_user(db_id, user_id)
|
|
937
|
+
if response.status_code == 204:
|
|
938
|
+
print(user_id)
|
|
939
|
+
else:
|
|
940
|
+
sys.exit(fmt.printer(response))
|
|
941
|
+
|
|
942
|
+
|
|
943
|
+
# ------------------------------------------------------------- #
|
|
944
|
+
# $ twc database instance list #
|
|
945
|
+
# ------------------------------------------------------------- #
|
|
946
|
+
|
|
947
|
+
|
|
948
|
+
def _print_database_instances(response: Response):
|
|
949
|
+
instances = response.json()["instances"]
|
|
950
|
+
table = fmt.Table()
|
|
951
|
+
table.header(["ID", "NAME", "CREATED", "DESCRIPTION"])
|
|
952
|
+
for i in instances:
|
|
953
|
+
table.row(
|
|
954
|
+
[
|
|
955
|
+
i["id"],
|
|
956
|
+
i["name"],
|
|
957
|
+
i["created_at"],
|
|
958
|
+
i["description"],
|
|
959
|
+
]
|
|
960
|
+
)
|
|
961
|
+
table.print()
|
|
962
|
+
|
|
963
|
+
|
|
964
|
+
@database_instance.command("list", "ls")
|
|
965
|
+
def database_instance_list(
|
|
966
|
+
db_id: int,
|
|
967
|
+
verbose: Optional[bool] = verbose_option,
|
|
968
|
+
config: Optional[Path] = config_option,
|
|
969
|
+
profile: Optional[str] = profile_option,
|
|
970
|
+
output_format: Optional[str] = output_format_option,
|
|
971
|
+
):
|
|
972
|
+
"""List databases in database cluster."""
|
|
973
|
+
client = create_client(config, profile)
|
|
974
|
+
response = client.get_database_instances(db_id)
|
|
975
|
+
fmt.printer(
|
|
976
|
+
response,
|
|
977
|
+
output_format=output_format,
|
|
978
|
+
func=_print_database_instances,
|
|
979
|
+
)
|
|
980
|
+
|
|
981
|
+
|
|
982
|
+
# ------------------------------------------------------------- #
|
|
983
|
+
# $ twc database instance create #
|
|
984
|
+
# ------------------------------------------------------------- #
|
|
985
|
+
|
|
986
|
+
|
|
987
|
+
@database_instance.command("create")
|
|
988
|
+
def database_instance_create(
|
|
989
|
+
db_id: int,
|
|
990
|
+
name: str,
|
|
991
|
+
verbose: Optional[bool] = verbose_option,
|
|
992
|
+
config: Optional[Path] = config_option,
|
|
993
|
+
profile: Optional[str] = profile_option,
|
|
994
|
+
output_format: Optional[str] = output_format_option,
|
|
995
|
+
desc: Optional[str] = typer.Option(None, help="Comment for database."),
|
|
996
|
+
):
|
|
997
|
+
"""Create database in database cluster."""
|
|
998
|
+
client = create_client(config, profile)
|
|
999
|
+
response = client.create_database_instance(
|
|
1000
|
+
db_id, name=name, description=desc
|
|
1001
|
+
)
|
|
1002
|
+
fmt.printer(
|
|
1003
|
+
response,
|
|
1004
|
+
output_format=output_format,
|
|
1005
|
+
func=lambda response: print(response.json()["instance"]["id"]),
|
|
1006
|
+
)
|
|
1007
|
+
|
|
1008
|
+
|
|
1009
|
+
# ------------------------------------------------------------- #
|
|
1010
|
+
# $ twc database instance remove #
|
|
1011
|
+
# ------------------------------------------------------------- #
|
|
1012
|
+
|
|
1013
|
+
|
|
1014
|
+
@database_instance.command("remove", "rm")
|
|
1015
|
+
def database_instance_remove(
|
|
1016
|
+
db_id: int,
|
|
1017
|
+
instance_id: int,
|
|
1018
|
+
verbose: Optional[bool] = verbose_option,
|
|
1019
|
+
config: Optional[Path] = config_option,
|
|
1020
|
+
profile: Optional[str] = profile_option,
|
|
1021
|
+
):
|
|
1022
|
+
"""Delete database from cluster."""
|
|
1023
|
+
client = create_client(config, profile)
|
|
1024
|
+
response = client.get_database_user(db_id, instance_id)
|
|
1025
|
+
if response.status_code == 204:
|
|
1026
|
+
print(instance_id)
|
|
1027
|
+
else:
|
|
1028
|
+
sys.exit(fmt.printer(response))
|
twc/commands/server.py
CHANGED
|
@@ -558,7 +558,10 @@ def server_create(
|
|
|
558
558
|
help="Enable LAN.",
|
|
559
559
|
hidden=True,
|
|
560
560
|
),
|
|
561
|
-
network: Optional[str] = typer.Option(
|
|
561
|
+
network: Optional[str] = typer.Option(
|
|
562
|
+
None, hidden=True, help="Private network ID."
|
|
563
|
+
),
|
|
564
|
+
network_id: Optional[str] = typer.Option(None, help="Private network ID."),
|
|
562
565
|
private_ip: Optional[str] = typer.Option(
|
|
563
566
|
None, help="Private IPv4 address."
|
|
564
567
|
),
|
|
@@ -631,14 +634,21 @@ def server_create(
|
|
|
631
634
|
)
|
|
632
635
|
|
|
633
636
|
# Set network parameters
|
|
634
|
-
if nat_mode or private_ip:
|
|
635
|
-
if not network:
|
|
636
|
-
sys.exit("Error: Pass '--network' option first.")
|
|
637
637
|
if network:
|
|
638
|
-
|
|
638
|
+
print(
|
|
639
|
+
"Option --network is deprecated and will be removed soon, "
|
|
640
|
+
"use --network-id instead",
|
|
641
|
+
file=sys.stderr,
|
|
642
|
+
)
|
|
643
|
+
network_id = network
|
|
644
|
+
if nat_mode or private_ip:
|
|
645
|
+
if not network_id:
|
|
646
|
+
sys.exit("Error: Pass '--network-id' option first.")
|
|
647
|
+
if network_id:
|
|
648
|
+
payload["network"]["id"] = network_id
|
|
639
649
|
if private_ip:
|
|
640
650
|
net = IPv4Network(
|
|
641
|
-
client.get_vpc(
|
|
651
|
+
client.get_vpc(network_id).json()["vpc"]["subnet_v4"]
|
|
642
652
|
)
|
|
643
653
|
if IPv4Address(private_ip) >= net.network_address + 4:
|
|
644
654
|
payload["network"]["ip"] = private_ip
|
twc/vars.py
CHANGED
|
@@ -8,7 +8,15 @@ expand or other infrastructure or product changes occur.
|
|
|
8
8
|
CONTROL_PANEL_URL = "https://timeweb.cloud/my"
|
|
9
9
|
|
|
10
10
|
# Location specific parameters. May change later.
|
|
11
|
-
REGIONS_WITH_IPV6 = ["ru-1", "pl-1"]
|
|
11
|
+
REGIONS_WITH_IPV6 = ["ru-1", "ru-3", "pl-1", "nl-1"]
|
|
12
12
|
REGIONS_WITH_IMAGES = ["ru-1", "ru-3", "kz-1", "pl-1", "nl-1"]
|
|
13
|
-
REGIONS_WITH_LAN = ["ru-1", "ru-3", "nl-1", "pl-1"]
|
|
14
|
-
ZONES_WITH_LAN = [
|
|
13
|
+
REGIONS_WITH_LAN = ["ru-1", "ru-3", "nl-1", "pl-1", "de-1"]
|
|
14
|
+
ZONES_WITH_LAN = [
|
|
15
|
+
"spb-1",
|
|
16
|
+
"spb-3",
|
|
17
|
+
"spb-4",
|
|
18
|
+
"msk-1",
|
|
19
|
+
"ams-1",
|
|
20
|
+
"gdn-1",
|
|
21
|
+
"fra-1",
|
|
22
|
+
]
|
|
@@ -1,36 +1,36 @@
|
|
|
1
|
-
CHANGELOG.md,sha256=
|
|
1
|
+
CHANGELOG.md,sha256=9i0MuhpW4DSXw848ZGwm7AdA-XA9rYyOigBRT2T76cU,29632
|
|
2
2
|
COPYING,sha256=fpJLxZS_kHr_659xkpmqEtqHeZp6lQR9CmKCwnYbsmE,1089
|
|
3
3
|
twc/__init__.py,sha256=NwPAMNw3NuHdWGQvWS9_lromVF6VM194oVOipojfJns,113
|
|
4
4
|
twc/__main__.py,sha256=ADHceaQUzgLmvhYHvb5O8urdJWj5IcEHLpTQkSExiD8,2468
|
|
5
|
-
twc/__version__.py,sha256=
|
|
5
|
+
twc/__version__.py,sha256=hlCsp4lDiHyiPjydCpKPl4urI6KoC8-yx7qcaoAret8,443
|
|
6
6
|
twc/api/__init__.py,sha256=SXew0Fe51M7nRBNQaaLRH4NjnRHkQUn7J26OCkQsftA,128
|
|
7
7
|
twc/api/base.py,sha256=QRefnIgmlbz8n37GLBKeAK1AtzkcNo1IFjZgHDDECJ4,7912
|
|
8
|
-
twc/api/client.py,sha256=
|
|
8
|
+
twc/api/client.py,sha256=T74KLeKHyAxzKHkpdNF-VOkfiAwnu7us61xzPosV5_o,64366
|
|
9
9
|
twc/api/exceptions.py,sha256=UzK3pKRffcXlhnkPy6MDjP_DygVoV17DuZ_mdNbOzts,2369
|
|
10
|
-
twc/api/types.py,sha256=
|
|
10
|
+
twc/api/types.py,sha256=uagnD3TPpoJFYUFK6HfHQPlEXs2GLxuJdjhNIbraXwM,5374
|
|
11
11
|
twc/apiwrap.py,sha256=hKrg_o6rLfY32SEnWMc1BSXHnSAh7TGar1JQ90YnG5M,2970
|
|
12
12
|
twc/commands/__init__.py,sha256=a-6fHQQwOj--Z7uBZGZL3z1rvJiOGUMQMRET1UknIYo,430
|
|
13
13
|
twc/commands/account.py,sha256=6q9ri02oFbUUZuqNVXO-uHOX45B4ELJlPjyfVaEL5Qw,5960
|
|
14
14
|
twc/commands/balancer.py,sha256=QAouc74ZT5go11gB1vjjfYtd1luTmWrfpACPwokZ5sU,20278
|
|
15
15
|
twc/commands/common.py,sha256=Wph8cVogUNNvc456SQrASb7mv7G88I8ETwHgISVjLQQ,8282
|
|
16
16
|
twc/commands/config.py,sha256=xHNEZVmM60c9dApLfNsj78sXZk6VsFwPdVIHO9r8xks,8802
|
|
17
|
-
twc/commands/database.py,sha256=
|
|
17
|
+
twc/commands/database.py,sha256=dX0z8LLpVTfCM7QNe0tU1DH7IWIwaJZBdSqHs6hWzNQ,31446
|
|
18
18
|
twc/commands/domain.py,sha256=BIg5k0TDQ-iWnhjuAHaWlZBB0bfaZgqZ2EWZGk3BICA,17154
|
|
19
19
|
twc/commands/firewall.py,sha256=KNolqbi2rsppOZwbs_j3yoZQt-0wKbj1JPGiZdfGxDE,27439
|
|
20
20
|
twc/commands/floating_ip.py,sha256=G9nD5BbHCZcuytbzeneDJWQDhd8c8WRtq9pAfwI9m7E,8747
|
|
21
21
|
twc/commands/image.py,sha256=OviQwegXK55H3TBlroCASVcgj2QUVCTo0ZhF5ug9eT8,8165
|
|
22
22
|
twc/commands/kubernetes.py,sha256=-Cgas1vFVMcrWGinjstuUz3sqX0ZNXv_4mwPwuwKeLE,20870
|
|
23
23
|
twc/commands/project.py,sha256=xnL3kLIumKzrI9EZ6r6m-PGOl3mZ9IhLQua7WZ3Rghg,10499
|
|
24
|
-
twc/commands/server.py,sha256=
|
|
24
|
+
twc/commands/server.py,sha256=5yb_pdB5BOoj_UAWdMxiCtuGdRBgcllkStMqyRSlx9k,72315
|
|
25
25
|
twc/commands/ssh_key.py,sha256=NHgTPhAQpDzt-iPHHVo4XqUJvujNqf019N6N9qYZ9Us,7941
|
|
26
26
|
twc/commands/storage.py,sha256=Pztk5iUBp9RtkdOwsfHaZFCnD8GuH6zOPtluawkRmiI,19404
|
|
27
27
|
twc/commands/vpc.py,sha256=SAht6UD17mU0d_AZY6W34VEYs7CqUsS2iDakPFxAFQU,8876
|
|
28
28
|
twc/fmt.py,sha256=nbuYZ8nVabYDwCmZqnL3-c6Tmri4B-R_sTCkG6sdfeI,7171
|
|
29
29
|
twc/typerx.py,sha256=AZ6BgTQvlrZYfKVYd9YqRNQnAR2XuyqImz4rf6di6f4,6737
|
|
30
30
|
twc/utils.py,sha256=uWizyUC4dHLwtk50q4Sub3zOvnVESfHKBbXYwk5t71w,651
|
|
31
|
-
twc/vars.py,sha256=
|
|
32
|
-
twc_cli-2.
|
|
33
|
-
twc_cli-2.
|
|
34
|
-
twc_cli-2.
|
|
35
|
-
twc_cli-2.
|
|
36
|
-
twc_cli-2.
|
|
31
|
+
twc/vars.py,sha256=XRtd77WWy2Ym2Yaodx84CKqKu2272O-Bj_fMFCjzuwg,607
|
|
32
|
+
twc_cli-2.10.0.dist-info/COPYING,sha256=fpJLxZS_kHr_659xkpmqEtqHeZp6lQR9CmKCwnYbsmE,1089
|
|
33
|
+
twc_cli-2.10.0.dist-info/METADATA,sha256=ZTVW8woDo4IdmAsKC-Okd4tHJ6CKKNgBQ7pnKw5Yc98,2653
|
|
34
|
+
twc_cli-2.10.0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
|
|
35
|
+
twc_cli-2.10.0.dist-info/entry_points.txt,sha256=tmTaVRhm8BkNrXC_9XJMum7O9wFVOvkXcBetxmahWvE,40
|
|
36
|
+
twc_cli-2.10.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|