twc-cli 2.9.2__py3-none-any.whl → 2.10.1__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 +26 -0
- twc/__version__.py +1 -1
- twc/api/client.py +154 -5
- twc/api/types.py +6 -10
- twc/commands/database.py +542 -40
- twc/commands/server.py +16 -6
- twc/vars.py +21 -3
- {twc_cli-2.9.2.dist-info → twc_cli-2.10.1.dist-info}/METADATA +1 -1
- {twc_cli-2.9.2.dist-info → twc_cli-2.10.1.dist-info}/RECORD +12 -12
- {twc_cli-2.9.2.dist-info → twc_cli-2.10.1.dist-info}/COPYING +0 -0
- {twc_cli-2.9.2.dist-info → twc_cli-2.10.1.dist-info}/WHEEL +0 -0
- {twc_cli-2.9.2.dist-info → twc_cli-2.10.1.dist-info}/entry_points.txt +0 -0
CHANGELOG.md
CHANGED
|
@@ -2,6 +2,32 @@
|
|
|
2
2
|
|
|
3
3
|
В этом файле описаны все значимые изменения в Timeweb Cloud CLI. В выпусках мы придерживается правил [семантического версионирования](https://semver.org/lang/ru/).
|
|
4
4
|
|
|
5
|
+
# Версия 2.10.1 (2025.03.25)
|
|
6
|
+
|
|
7
|
+
## Исправлено
|
|
8
|
+
|
|
9
|
+
- Исправлена ошибка получения публичного IP при создании кластера СУБД.
|
|
10
|
+
|
|
11
|
+
# Версия 2.10.0 (2025.03.24)
|
|
12
|
+
|
|
13
|
+
## Добавлено
|
|
14
|
+
|
|
15
|
+
- Добавлена поддержка региона `de-1` (зона `fra-1`) для облачных серверов.
|
|
16
|
+
- Добавлены новые опции к команде `twc database create`:
|
|
17
|
+
- для создания пользователя СУБД: `--user-login`, `--user-password`, `--user-host`, `--user-privileges`, `--user-desc`;
|
|
18
|
+
- для создания первой базы данных: `--db-name`, `--db-desc`;
|
|
19
|
+
- для настроек сети в класетере СУБД: `--network-id`, `--private-ip`, `--public-ip`, `--no-public-ip`;
|
|
20
|
+
- для настройки автоматических бэкапов кластера: `--enable-backups`, `--backup-keep`, `--backup-start-date`, `--backup-interval`, `--backup-day-of-week`.
|
|
21
|
+
- Добавлена новая команда `twc database list-types` для вывода доступных к созданию управляемых баз данных.
|
|
22
|
+
- Добавлена новая команда `twc database backup schedule` позволяющая настроить параметры автоматического резервного копирования кластера.
|
|
23
|
+
- Добавлены новые команды для управления пользователями в кластерах СУБД: `twc database user list`, `twc database user get`, `twc database user create`, `twc database user remove`.
|
|
24
|
+
- Добавлены новые команды для управления базами данных в кластерах СУБД: `twc database instance list`, `twc database instance create`, `twc database instance remove`.
|
|
25
|
+
|
|
26
|
+
## Изменено
|
|
27
|
+
|
|
28
|
+
- Опция `--network` команды `twc server create` объявлена устаревшей и скрыта, добавлена эквивалентная опция `--network-id`.
|
|
29
|
+
- Опции `--login` и `--password` команды `twc database create` объявлены устаревшими и скрыты, вместо них теперь нужно использовать `--user-login` и `--user-password`.
|
|
30
|
+
|
|
5
31
|
# Версия 2.9.2 (2025.03.10)
|
|
6
32
|
|
|
7
33
|
## Исправлено
|
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,16 +2,20 @@
|
|
|
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
|
|
18
|
+
from twc.vars import REGION_ZONE_MAP
|
|
15
19
|
from twc.utils import merge_dicts
|
|
16
20
|
from .common import (
|
|
17
21
|
verbose_option,
|
|
@@ -21,12 +25,19 @@ from .common import (
|
|
|
21
25
|
yes_option,
|
|
22
26
|
output_format_option,
|
|
23
27
|
load_from_config_callback,
|
|
28
|
+
zone_option,
|
|
24
29
|
)
|
|
25
30
|
|
|
26
31
|
|
|
27
32
|
database = TyperAlias(help=__doc__)
|
|
28
33
|
database_backup = TyperAlias(help="Manage database backups.")
|
|
29
34
|
database.add_typer(database_backup, name="backup")
|
|
35
|
+
database_user = TyperAlias(help="Manage database users.")
|
|
36
|
+
database.add_typer(database_user, name="user")
|
|
37
|
+
database_instance = TyperAlias(
|
|
38
|
+
help="Manage instances in cluster (databases/topics/etc)."
|
|
39
|
+
)
|
|
40
|
+
database.add_typer(database_instance, name="instance", aliases=["db"])
|
|
30
41
|
|
|
31
42
|
|
|
32
43
|
# ------------------------------------------------------------- #
|
|
@@ -206,6 +217,34 @@ def database_list_presets(
|
|
|
206
217
|
)
|
|
207
218
|
|
|
208
219
|
|
|
220
|
+
# ------------------------------------------------------------- #
|
|
221
|
+
# $ twc database list-types #
|
|
222
|
+
# ------------------------------------------------------------- #
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
@database.command("list-types", "lt")
|
|
226
|
+
def database_list_types(
|
|
227
|
+
verbose: Optional[bool] = verbose_option,
|
|
228
|
+
config: Optional[Path] = config_option,
|
|
229
|
+
profile: Optional[str] = profile_option,
|
|
230
|
+
output_format: Optional[str] = output_format_option,
|
|
231
|
+
):
|
|
232
|
+
"""List database configuration presets."""
|
|
233
|
+
client = create_client(config, profile)
|
|
234
|
+
response = client.get_database_types().json()
|
|
235
|
+
table = fmt.Table()
|
|
236
|
+
table.header(["TYPE", "DATABASE", "VERSION"])
|
|
237
|
+
for service in response["types"]:
|
|
238
|
+
table.row(
|
|
239
|
+
[
|
|
240
|
+
service["type"],
|
|
241
|
+
service["name"],
|
|
242
|
+
service["version"],
|
|
243
|
+
]
|
|
244
|
+
)
|
|
245
|
+
table.print()
|
|
246
|
+
|
|
247
|
+
|
|
209
248
|
# ------------------------------------------------------------- #
|
|
210
249
|
# $ twc database create #
|
|
211
250
|
# ------------------------------------------------------------- #
|
|
@@ -228,87 +267,210 @@ def set_params(params: list) -> dict:
|
|
|
228
267
|
return parameters
|
|
229
268
|
|
|
230
269
|
|
|
270
|
+
def dbms_parameters_callback(value: str) -> List[str]:
|
|
271
|
+
if value:
|
|
272
|
+
return value.split(",")
|
|
273
|
+
return []
|
|
274
|
+
|
|
275
|
+
|
|
231
276
|
@database.command("create")
|
|
232
277
|
def database_create(
|
|
233
278
|
verbose: Optional[bool] = verbose_option,
|
|
234
279
|
config: Optional[Path] = config_option,
|
|
235
280
|
profile: Optional[str] = profile_option,
|
|
236
281
|
output_format: Optional[str] = output_format_option,
|
|
282
|
+
availability_zone: Optional[str] = zone_option,
|
|
237
283
|
preset_id: int = typer.Option(..., help="Database configuration preset."),
|
|
238
|
-
dbms:
|
|
284
|
+
dbms: str = typer.Option(
|
|
239
285
|
...,
|
|
240
286
|
"--type",
|
|
241
|
-
|
|
242
|
-
help="Database management system.",
|
|
287
|
+
help="Database management system. See TYPE in `twc database list-types`.",
|
|
243
288
|
),
|
|
244
289
|
hash_type: Optional[MySQLAuthPlugin] = typer.Option(
|
|
245
290
|
MySQLAuthPlugin.CACHING_SHA2.value,
|
|
246
291
|
case_sensitive=False,
|
|
247
292
|
help="Authentication plugin for MySQL.",
|
|
248
293
|
),
|
|
249
|
-
name: str = typer.Option(..., help="Database
|
|
294
|
+
name: str = typer.Option(..., help="Database cluster display name."),
|
|
250
295
|
params: Optional[List[str]] = typer.Option(
|
|
251
296
|
None,
|
|
252
297
|
"--param",
|
|
253
298
|
metavar="PARAM=VALUE",
|
|
254
|
-
help="Database parameters, can be multiple.",
|
|
299
|
+
help="Database config parameters, can be multiple.",
|
|
255
300
|
),
|
|
256
|
-
login: Optional[str] = typer.Option(
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
301
|
+
login: Optional[str] = typer.Option(
|
|
302
|
+
# DEPRECATED
|
|
303
|
+
None,
|
|
304
|
+
hidden=True,
|
|
305
|
+
help="Database user login.",
|
|
306
|
+
),
|
|
307
|
+
password: Optional[str] = typer.Option(
|
|
308
|
+
# DEPRECATED
|
|
309
|
+
None,
|
|
310
|
+
hidden=True,
|
|
311
|
+
),
|
|
312
|
+
user_login: Optional[str] = typer.Option(None, help="User login."),
|
|
313
|
+
user_password: Optional[str] = typer.Option(None, help="User password."),
|
|
314
|
+
user_host: Optional[str] = typer.Option(
|
|
315
|
+
"%", help="User host for MySQL, Postgres"
|
|
316
|
+
),
|
|
317
|
+
user_privileges: Optional[str] = typer.Option(
|
|
318
|
+
[],
|
|
319
|
+
help="Comma-separated list of user privileges.",
|
|
320
|
+
callback=dbms_parameters_callback,
|
|
321
|
+
),
|
|
322
|
+
user_desc: Optional[str] = typer.Option(None, help="Comment for user."),
|
|
323
|
+
db_name: Optional[str] = typer.Option(None, help="Database name."),
|
|
324
|
+
db_desc: Optional[str] = typer.Option(None, help="Database comment."),
|
|
325
|
+
network_id: Optional[str] = typer.Option(None, help="Private network ID."),
|
|
326
|
+
private_ip: Optional[str] = typer.Option(
|
|
327
|
+
None, help="Private IPv4 address."
|
|
328
|
+
),
|
|
329
|
+
public_ip: Optional[str] = typer.Option(
|
|
330
|
+
None, help="Public IPv4 address. New address by default."
|
|
331
|
+
),
|
|
332
|
+
no_public_ip: Optional[bool] = typer.Option(
|
|
333
|
+
False, "--no-public-ip", help="Do not add public IPv4 address."
|
|
262
334
|
),
|
|
263
335
|
project_id: Optional[int] = typer.Option(
|
|
264
336
|
None,
|
|
265
337
|
envvar="TWC_PROJECT",
|
|
266
338
|
show_envvar=False,
|
|
267
339
|
callback=load_from_config_callback,
|
|
268
|
-
help="Add database to specific project.",
|
|
340
|
+
help="Add database cluster to specific project.",
|
|
341
|
+
),
|
|
342
|
+
enable_backups: Optional[bool] = typer.Option(
|
|
343
|
+
False,
|
|
344
|
+
"--enable-backups",
|
|
345
|
+
help="Enable atomatic backups of database cluster.",
|
|
346
|
+
),
|
|
347
|
+
backup_keep: Optional[int] = typer.Option(
|
|
348
|
+
1,
|
|
349
|
+
show_default=True,
|
|
350
|
+
help="Number of backups to keep.",
|
|
351
|
+
),
|
|
352
|
+
backup_start_date: Optional[datetime] = typer.Option(
|
|
353
|
+
date.today().strftime("%Y-%m-%d"),
|
|
354
|
+
formats=["%Y-%m-%d"],
|
|
355
|
+
show_default=False,
|
|
356
|
+
help="Start date of the first backup creation [default: today].",
|
|
357
|
+
),
|
|
358
|
+
backup_interval: Optional[BackupInterval] = typer.Option(
|
|
359
|
+
BackupInterval.DAY.value,
|
|
360
|
+
"--backup-interval",
|
|
361
|
+
help="Backup interval.",
|
|
362
|
+
),
|
|
363
|
+
backup_day_of_week: Optional[int] = typer.Option(
|
|
364
|
+
1,
|
|
365
|
+
min=1,
|
|
366
|
+
max=7,
|
|
367
|
+
help="The day of the week on which backups will be created."
|
|
368
|
+
" NOTE: This option works only with interval 'week'."
|
|
369
|
+
" First day of week is monday.",
|
|
269
370
|
),
|
|
270
371
|
):
|
|
271
|
-
"""Create managed database
|
|
372
|
+
"""Create managed database cluster."""
|
|
272
373
|
client = create_client(config, profile)
|
|
273
374
|
|
|
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
375
|
payload = {
|
|
286
376
|
"dbms": dbms,
|
|
287
377
|
"preset_id": preset_id,
|
|
288
378
|
"name": name,
|
|
289
379
|
"hash_type": hash_type,
|
|
290
|
-
"login": login,
|
|
291
|
-
"password": password,
|
|
292
380
|
"config_parameters": {},
|
|
381
|
+
"network": {},
|
|
293
382
|
}
|
|
294
383
|
|
|
384
|
+
if enable_backups:
|
|
385
|
+
backup_start_date = backup_start_date.strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
386
|
+
payload["auto_backups"] = {
|
|
387
|
+
"copy_count": backup_keep,
|
|
388
|
+
"creation_started_at": backup_start_date,
|
|
389
|
+
"interval": backup_interval,
|
|
390
|
+
"day_of_week": backup_day_of_week,
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
if network_id:
|
|
394
|
+
payload["network"]["id"] = network_id
|
|
395
|
+
if private_ip:
|
|
396
|
+
net = IPv4Network(
|
|
397
|
+
client.get_vpc(network_id).json()["vpc"]["subnet_v4"]
|
|
398
|
+
)
|
|
399
|
+
if IPv4Address(private_ip) >= net.network_address + 4:
|
|
400
|
+
payload["network"]["ip"] = private_ip
|
|
401
|
+
else:
|
|
402
|
+
# First 3 addresses is reserved by Timeweb Cloud for gateway and future use.
|
|
403
|
+
sys.exit(
|
|
404
|
+
f"Error: Private address '{private_ip}' is not allowed. "
|
|
405
|
+
"IP must be at least the fourth in order in the network."
|
|
406
|
+
)
|
|
407
|
+
if public_ip:
|
|
408
|
+
try:
|
|
409
|
+
_ = IPv4Address(public_ip)
|
|
410
|
+
payload["network"]["floating_ip"] = public_ip
|
|
411
|
+
except ValueError:
|
|
412
|
+
sys.exit(f"Error: '{public_ip}' is not valid IPv4 address.")
|
|
413
|
+
else:
|
|
414
|
+
# Get new public IPv4 address.
|
|
415
|
+
if no_public_ip is False:
|
|
416
|
+
zone = None
|
|
417
|
+
if preset_id and not availability_zone:
|
|
418
|
+
for preset in client.get_database_presets().json()[
|
|
419
|
+
"databases_presets"
|
|
420
|
+
]:
|
|
421
|
+
if preset["id"] == preset_id:
|
|
422
|
+
zone = REGION_ZONE_MAP[preset["location"]]
|
|
423
|
+
if availability_zone:
|
|
424
|
+
zone = availability_zone
|
|
425
|
+
ip = client.create_floating_ip(availability_zone=zone).json()[
|
|
426
|
+
"ip"
|
|
427
|
+
]["ip"]
|
|
428
|
+
payload["network"]["floating_ip"] = ip
|
|
429
|
+
|
|
430
|
+
if login:
|
|
431
|
+
print(
|
|
432
|
+
"--login is deprecated use --user-login instead", file=sys.stderr
|
|
433
|
+
)
|
|
434
|
+
user_login = login
|
|
435
|
+
|
|
436
|
+
if password:
|
|
437
|
+
print(
|
|
438
|
+
"--password is deprecated use --user-password instead",
|
|
439
|
+
file=sys.stderr,
|
|
440
|
+
)
|
|
441
|
+
user_password = password
|
|
442
|
+
|
|
443
|
+
if user_password and not user_login:
|
|
444
|
+
sys.exit("Error: --user-login required if --user-password is set.")
|
|
445
|
+
|
|
446
|
+
if user_login:
|
|
447
|
+
if not user_password:
|
|
448
|
+
user_password = typer.prompt(
|
|
449
|
+
"Database user password",
|
|
450
|
+
hide_input=True,
|
|
451
|
+
confirmation_prompt=True,
|
|
452
|
+
)
|
|
453
|
+
payload["admin"] = {
|
|
454
|
+
"login": user_login,
|
|
455
|
+
"password": user_password,
|
|
456
|
+
"host": user_host,
|
|
457
|
+
"privileges": user_privileges,
|
|
458
|
+
"description": user_desc or "",
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
if db_name:
|
|
462
|
+
payload["instance"] = {
|
|
463
|
+
"name": db_name,
|
|
464
|
+
"description": db_desc or "",
|
|
465
|
+
}
|
|
466
|
+
|
|
295
467
|
if params:
|
|
296
468
|
payload["config_parameters"] = set_params(params)
|
|
297
469
|
|
|
298
470
|
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.")
|
|
471
|
+
payload["project_id"] = project_id
|
|
303
472
|
|
|
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
|
-
)
|
|
473
|
+
response = client.create_database2(**payload)
|
|
312
474
|
|
|
313
475
|
fmt.printer(
|
|
314
476
|
response,
|
|
@@ -537,4 +699,344 @@ def database_backup_restore(
|
|
|
537
699
|
# $ twc database backup schedule #
|
|
538
700
|
# ------------------------------------------------------------- #
|
|
539
701
|
|
|
540
|
-
|
|
702
|
+
|
|
703
|
+
def print_autobackup_settings(response: Response):
|
|
704
|
+
"""Print backup settings info."""
|
|
705
|
+
table = fmt.Table()
|
|
706
|
+
settings = response.json()["auto_backups_settings"]
|
|
707
|
+
translated_keys = {
|
|
708
|
+
"copy_count": "Keep copies",
|
|
709
|
+
"creation_start_at": "Backup start date",
|
|
710
|
+
"is_enabled": "Enabled",
|
|
711
|
+
"interval": "Interval",
|
|
712
|
+
"day_of_week": "Day of week",
|
|
713
|
+
}
|
|
714
|
+
for key in settings.keys():
|
|
715
|
+
table.row([translated_keys[key], ":", settings[key]])
|
|
716
|
+
table.print()
|
|
717
|
+
|
|
718
|
+
|
|
719
|
+
@database_backup.command("schedule")
|
|
720
|
+
def database_backup_schedule(
|
|
721
|
+
db_id: int,
|
|
722
|
+
verbose: Optional[bool] = verbose_option,
|
|
723
|
+
config: Optional[Path] = config_option,
|
|
724
|
+
profile: Optional[str] = profile_option,
|
|
725
|
+
output_format: Optional[str] = output_format_option,
|
|
726
|
+
status: bool = typer.Option(
|
|
727
|
+
False,
|
|
728
|
+
"--status",
|
|
729
|
+
help="Display automatic backups status.",
|
|
730
|
+
),
|
|
731
|
+
enable: Optional[bool] = typer.Option(
|
|
732
|
+
None,
|
|
733
|
+
"--enable/--disable",
|
|
734
|
+
show_default=False,
|
|
735
|
+
help="Enable or disable automatic backups.",
|
|
736
|
+
),
|
|
737
|
+
keep: int = typer.Option(
|
|
738
|
+
1,
|
|
739
|
+
show_default=True,
|
|
740
|
+
help="Number of backups to keep.",
|
|
741
|
+
),
|
|
742
|
+
start_date: datetime = typer.Option(
|
|
743
|
+
date.today().strftime("%Y-%m-%d"),
|
|
744
|
+
formats=["%Y-%m-%d"],
|
|
745
|
+
show_default=False,
|
|
746
|
+
help="Start date of the first backup creation [default: today].",
|
|
747
|
+
),
|
|
748
|
+
interval: BackupInterval = typer.Option(
|
|
749
|
+
BackupInterval.DAY.value,
|
|
750
|
+
"--interval",
|
|
751
|
+
help="Backup interval.",
|
|
752
|
+
),
|
|
753
|
+
day_of_week: Optional[int] = typer.Option(
|
|
754
|
+
1,
|
|
755
|
+
min=1,
|
|
756
|
+
max=7,
|
|
757
|
+
help="The day of the week on which backups will be created."
|
|
758
|
+
" NOTE: This option works only with interval 'week'."
|
|
759
|
+
" First day of week is monday.",
|
|
760
|
+
),
|
|
761
|
+
):
|
|
762
|
+
"""Manage database cluster automatic backup settings."""
|
|
763
|
+
client = create_client(config, profile)
|
|
764
|
+
|
|
765
|
+
if status:
|
|
766
|
+
response = client.get_database_autobackup_settings(db_id)
|
|
767
|
+
fmt.printer(
|
|
768
|
+
response,
|
|
769
|
+
output_format=output_format,
|
|
770
|
+
func=print_autobackup_settings,
|
|
771
|
+
)
|
|
772
|
+
if response.json()["auto_backups_settings"]["is_enabled"]:
|
|
773
|
+
sys.exit(0)
|
|
774
|
+
else:
|
|
775
|
+
sys.exit(1)
|
|
776
|
+
|
|
777
|
+
start_date = start_date.strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
778
|
+
|
|
779
|
+
response = client.update_database_autobackup_settings(
|
|
780
|
+
db_id,
|
|
781
|
+
is_enabled=enable,
|
|
782
|
+
copy_count=keep,
|
|
783
|
+
creation_start_at=start_date,
|
|
784
|
+
interval=interval,
|
|
785
|
+
day_of_week=day_of_week,
|
|
786
|
+
)
|
|
787
|
+
fmt.printer(
|
|
788
|
+
response,
|
|
789
|
+
output_format=output_format,
|
|
790
|
+
func=print_autobackup_settings,
|
|
791
|
+
)
|
|
792
|
+
|
|
793
|
+
|
|
794
|
+
# ------------------------------------------------------------- #
|
|
795
|
+
# $ twc database user list #
|
|
796
|
+
# ------------------------------------------------------------- #
|
|
797
|
+
|
|
798
|
+
|
|
799
|
+
def _print_database_users(response: Response):
|
|
800
|
+
users = response.json()["admins"]
|
|
801
|
+
table = fmt.Table()
|
|
802
|
+
table.header(["ID", "LOGIN", "HOST", "CREATED"])
|
|
803
|
+
for user in users:
|
|
804
|
+
table.row(
|
|
805
|
+
[
|
|
806
|
+
user["id"],
|
|
807
|
+
user["login"],
|
|
808
|
+
user["host"],
|
|
809
|
+
user["created_at"],
|
|
810
|
+
]
|
|
811
|
+
)
|
|
812
|
+
table.print()
|
|
813
|
+
|
|
814
|
+
|
|
815
|
+
@database_user.command("list", "ls")
|
|
816
|
+
def database_user_list(
|
|
817
|
+
db_id: int,
|
|
818
|
+
verbose: Optional[bool] = verbose_option,
|
|
819
|
+
config: Optional[Path] = config_option,
|
|
820
|
+
profile: Optional[str] = profile_option,
|
|
821
|
+
output_format: Optional[str] = output_format_option,
|
|
822
|
+
):
|
|
823
|
+
"""List database users."""
|
|
824
|
+
client = create_client(config, profile)
|
|
825
|
+
response = client.get_database_users(db_id)
|
|
826
|
+
fmt.printer(
|
|
827
|
+
response,
|
|
828
|
+
output_format=output_format,
|
|
829
|
+
func=_print_database_users,
|
|
830
|
+
)
|
|
831
|
+
|
|
832
|
+
|
|
833
|
+
# ------------------------------------------------------------- #
|
|
834
|
+
# $ twc database user get #
|
|
835
|
+
# ------------------------------------------------------------- #
|
|
836
|
+
|
|
837
|
+
|
|
838
|
+
def _print_database_user(response: Response):
|
|
839
|
+
user = response.json()["admin"]
|
|
840
|
+
out = textwrap.dedent(
|
|
841
|
+
f"""
|
|
842
|
+
Login: {user['login']}
|
|
843
|
+
Host: {user['host']}
|
|
844
|
+
Created: {user['created_at']}
|
|
845
|
+
Description: {user['description']}
|
|
846
|
+
"""
|
|
847
|
+
).strip()
|
|
848
|
+
print(out)
|
|
849
|
+
print()
|
|
850
|
+
table = fmt.Table()
|
|
851
|
+
table.header(["INSTANCE_ID", "PRIVILEGES"])
|
|
852
|
+
for instance in user["instances"]:
|
|
853
|
+
table.row(
|
|
854
|
+
[
|
|
855
|
+
instance["instance_id"],
|
|
856
|
+
", ".join(instance["privileges"]),
|
|
857
|
+
]
|
|
858
|
+
)
|
|
859
|
+
table.print()
|
|
860
|
+
|
|
861
|
+
|
|
862
|
+
@database_user.command("get")
|
|
863
|
+
def database_user_get(
|
|
864
|
+
db_id: int,
|
|
865
|
+
user_id: int,
|
|
866
|
+
verbose: Optional[bool] = verbose_option,
|
|
867
|
+
config: Optional[Path] = config_option,
|
|
868
|
+
profile: Optional[str] = profile_option,
|
|
869
|
+
output_format: Optional[str] = output_format_option,
|
|
870
|
+
):
|
|
871
|
+
"""Get database user."""
|
|
872
|
+
client = create_client(config, profile)
|
|
873
|
+
response = client.get_database_user(db_id, user_id)
|
|
874
|
+
fmt.printer(
|
|
875
|
+
response,
|
|
876
|
+
output_format=output_format,
|
|
877
|
+
func=_print_database_user,
|
|
878
|
+
)
|
|
879
|
+
|
|
880
|
+
|
|
881
|
+
# ------------------------------------------------------------- #
|
|
882
|
+
# $ twc database user create #
|
|
883
|
+
# ------------------------------------------------------------- #
|
|
884
|
+
|
|
885
|
+
|
|
886
|
+
@database_user.command("create")
|
|
887
|
+
def database_user_create(
|
|
888
|
+
db_id: int,
|
|
889
|
+
verbose: Optional[bool] = verbose_option,
|
|
890
|
+
config: Optional[Path] = config_option,
|
|
891
|
+
profile: Optional[str] = profile_option,
|
|
892
|
+
output_format: Optional[str] = output_format_option,
|
|
893
|
+
login: str = typer.Option(..., help="User login."),
|
|
894
|
+
password: str = typer.Option(
|
|
895
|
+
...,
|
|
896
|
+
prompt=True,
|
|
897
|
+
hide_input=True,
|
|
898
|
+
confirmation_prompt=True,
|
|
899
|
+
help="User password.",
|
|
900
|
+
),
|
|
901
|
+
host: Optional[str] = typer.Option(
|
|
902
|
+
"%", help="User host for MySQL, Postgres"
|
|
903
|
+
),
|
|
904
|
+
instance_id: Optional[int] = typer.Option(
|
|
905
|
+
None,
|
|
906
|
+
help="The specific instance ID to which the privileges will be "
|
|
907
|
+
"applied. If not specified, the privileges will be applied to "
|
|
908
|
+
"all available instances.",
|
|
909
|
+
),
|
|
910
|
+
privileges: Optional[str] = typer.Option(
|
|
911
|
+
[],
|
|
912
|
+
help="Comma-separated list of user privileges.",
|
|
913
|
+
callback=dbms_parameters_callback,
|
|
914
|
+
),
|
|
915
|
+
desc: Optional[str] = typer.Option(None, help="Comment for user."),
|
|
916
|
+
):
|
|
917
|
+
"""Create database users."""
|
|
918
|
+
client = create_client(config, profile)
|
|
919
|
+
response = client.create_database_user(
|
|
920
|
+
db_id=db_id,
|
|
921
|
+
login=login,
|
|
922
|
+
password=password,
|
|
923
|
+
privileges=privileges,
|
|
924
|
+
host=host,
|
|
925
|
+
instance_id=instance_id,
|
|
926
|
+
description=desc,
|
|
927
|
+
)
|
|
928
|
+
fmt.printer(
|
|
929
|
+
response,
|
|
930
|
+
output_format=output_format,
|
|
931
|
+
func=lambda response: print(response.json()["admin"]["id"]),
|
|
932
|
+
)
|
|
933
|
+
|
|
934
|
+
|
|
935
|
+
# ------------------------------------------------------------- #
|
|
936
|
+
# $ twc database user remove #
|
|
937
|
+
# ------------------------------------------------------------- #
|
|
938
|
+
|
|
939
|
+
|
|
940
|
+
@database_user.command("remove", "rm")
|
|
941
|
+
def database_user_remove(
|
|
942
|
+
db_id: int,
|
|
943
|
+
user_id: int,
|
|
944
|
+
verbose: Optional[bool] = verbose_option,
|
|
945
|
+
config: Optional[Path] = config_option,
|
|
946
|
+
profile: Optional[str] = profile_option,
|
|
947
|
+
):
|
|
948
|
+
"""Delete database user."""
|
|
949
|
+
client = create_client(config, profile)
|
|
950
|
+
response = client.get_database_user(db_id, user_id)
|
|
951
|
+
if response.status_code == 204:
|
|
952
|
+
print(user_id)
|
|
953
|
+
else:
|
|
954
|
+
sys.exit(fmt.printer(response))
|
|
955
|
+
|
|
956
|
+
|
|
957
|
+
# ------------------------------------------------------------- #
|
|
958
|
+
# $ twc database instance list #
|
|
959
|
+
# ------------------------------------------------------------- #
|
|
960
|
+
|
|
961
|
+
|
|
962
|
+
def _print_database_instances(response: Response):
|
|
963
|
+
instances = response.json()["instances"]
|
|
964
|
+
table = fmt.Table()
|
|
965
|
+
table.header(["ID", "NAME", "CREATED", "DESCRIPTION"])
|
|
966
|
+
for i in instances:
|
|
967
|
+
table.row(
|
|
968
|
+
[
|
|
969
|
+
i["id"],
|
|
970
|
+
i["name"],
|
|
971
|
+
i["created_at"],
|
|
972
|
+
i["description"],
|
|
973
|
+
]
|
|
974
|
+
)
|
|
975
|
+
table.print()
|
|
976
|
+
|
|
977
|
+
|
|
978
|
+
@database_instance.command("list", "ls")
|
|
979
|
+
def database_instance_list(
|
|
980
|
+
db_id: int,
|
|
981
|
+
verbose: Optional[bool] = verbose_option,
|
|
982
|
+
config: Optional[Path] = config_option,
|
|
983
|
+
profile: Optional[str] = profile_option,
|
|
984
|
+
output_format: Optional[str] = output_format_option,
|
|
985
|
+
):
|
|
986
|
+
"""List databases in database cluster."""
|
|
987
|
+
client = create_client(config, profile)
|
|
988
|
+
response = client.get_database_instances(db_id)
|
|
989
|
+
fmt.printer(
|
|
990
|
+
response,
|
|
991
|
+
output_format=output_format,
|
|
992
|
+
func=_print_database_instances,
|
|
993
|
+
)
|
|
994
|
+
|
|
995
|
+
|
|
996
|
+
# ------------------------------------------------------------- #
|
|
997
|
+
# $ twc database instance create #
|
|
998
|
+
# ------------------------------------------------------------- #
|
|
999
|
+
|
|
1000
|
+
|
|
1001
|
+
@database_instance.command("create")
|
|
1002
|
+
def database_instance_create(
|
|
1003
|
+
db_id: int,
|
|
1004
|
+
name: str,
|
|
1005
|
+
verbose: Optional[bool] = verbose_option,
|
|
1006
|
+
config: Optional[Path] = config_option,
|
|
1007
|
+
profile: Optional[str] = profile_option,
|
|
1008
|
+
output_format: Optional[str] = output_format_option,
|
|
1009
|
+
desc: Optional[str] = typer.Option(None, help="Comment for database."),
|
|
1010
|
+
):
|
|
1011
|
+
"""Create database in database cluster."""
|
|
1012
|
+
client = create_client(config, profile)
|
|
1013
|
+
response = client.create_database_instance(
|
|
1014
|
+
db_id, name=name, description=desc
|
|
1015
|
+
)
|
|
1016
|
+
fmt.printer(
|
|
1017
|
+
response,
|
|
1018
|
+
output_format=output_format,
|
|
1019
|
+
func=lambda response: print(response.json()["instance"]["id"]),
|
|
1020
|
+
)
|
|
1021
|
+
|
|
1022
|
+
|
|
1023
|
+
# ------------------------------------------------------------- #
|
|
1024
|
+
# $ twc database instance remove #
|
|
1025
|
+
# ------------------------------------------------------------- #
|
|
1026
|
+
|
|
1027
|
+
|
|
1028
|
+
@database_instance.command("remove", "rm")
|
|
1029
|
+
def database_instance_remove(
|
|
1030
|
+
db_id: int,
|
|
1031
|
+
instance_id: int,
|
|
1032
|
+
verbose: Optional[bool] = verbose_option,
|
|
1033
|
+
config: Optional[Path] = config_option,
|
|
1034
|
+
profile: Optional[str] = profile_option,
|
|
1035
|
+
):
|
|
1036
|
+
"""Delete database from cluster."""
|
|
1037
|
+
client = create_client(config, profile)
|
|
1038
|
+
response = client.get_database_user(db_id, instance_id)
|
|
1039
|
+
if response.status_code == 204:
|
|
1040
|
+
print(instance_id)
|
|
1041
|
+
else:
|
|
1042
|
+
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,25 @@ 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
|
+
]
|
|
23
|
+
# The default availability zones per regions.
|
|
24
|
+
REGION_ZONE_MAP = {
|
|
25
|
+
"ru-1": "spb-3",
|
|
26
|
+
"ru-2": "nsk-1",
|
|
27
|
+
"ru-3": "msk-1",
|
|
28
|
+
"kz-1": "ala-1",
|
|
29
|
+
"pl-1": "gdn-1",
|
|
30
|
+
"nl-1": "ams-1",
|
|
31
|
+
"de-1": "fra-1",
|
|
32
|
+
}
|
|
@@ -1,36 +1,36 @@
|
|
|
1
|
-
CHANGELOG.md,sha256=
|
|
1
|
+
CHANGELOG.md,sha256=Qs4nN5qu8rQUqUgHQoGBQThGm0k5BaTLOx6BE6NiXWY,29824
|
|
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=8icMDOUZhpUZ9qC5kE9GWifcKzo2RYQElI1jYfMVJCs,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=NOi5b-DGYgbbN7bPrsJt0wKTJBzrfKUPwltVx8YGsgU,31953
|
|
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=AHZEwtQ_BQbnP0n7RXJ-qhqeKPQ_FPc8SDgg6osR5uU,822
|
|
32
|
+
twc_cli-2.10.1.dist-info/COPYING,sha256=fpJLxZS_kHr_659xkpmqEtqHeZp6lQR9CmKCwnYbsmE,1089
|
|
33
|
+
twc_cli-2.10.1.dist-info/METADATA,sha256=-BCPh3qtm2gn6edv2ih7nODdb0M0n8k8LMz2sGc5Dbs,2653
|
|
34
|
+
twc_cli-2.10.1.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
|
|
35
|
+
twc_cli-2.10.1.dist-info/entry_points.txt,sha256=tmTaVRhm8BkNrXC_9XJMum7O9wFVOvkXcBetxmahWvE,40
|
|
36
|
+
twc_cli-2.10.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|