twc-cli 2.11.0__tar.gz → 2.13.0__tar.gz
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.
- {twc_cli-2.11.0 → twc_cli-2.13.0}/CHANGELOG.md +17 -0
- {twc_cli-2.11.0 → twc_cli-2.13.0}/PKG-INFO +4 -4
- {twc_cli-2.11.0 → twc_cli-2.13.0}/pyproject.toml +2 -2
- {twc_cli-2.11.0 → twc_cli-2.13.0}/twc/__main__.py +2 -0
- {twc_cli-2.11.0 → twc_cli-2.13.0}/twc/__version__.py +1 -1
- {twc_cli-2.11.0 → twc_cli-2.13.0}/twc/api/client.py +101 -2
- {twc_cli-2.11.0 → twc_cli-2.13.0}/twc/commands/__init__.py +1 -0
- twc_cli-2.13.0/twc/commands/apps.py +337 -0
- {twc_cli-2.11.0 → twc_cli-2.13.0}/twc/commands/balancer.py +42 -0
- {twc_cli-2.11.0 → twc_cli-2.13.0}/twc/commands/storage.py +1 -1
- {twc_cli-2.11.0 → twc_cli-2.13.0}/twc/vars.py +1 -1
- {twc_cli-2.11.0 → twc_cli-2.13.0}/COPYING +0 -0
- {twc_cli-2.11.0 → twc_cli-2.13.0}/README.md +0 -0
- {twc_cli-2.11.0 → twc_cli-2.13.0}/twc/__init__.py +0 -0
- {twc_cli-2.11.0 → twc_cli-2.13.0}/twc/api/__init__.py +0 -0
- {twc_cli-2.11.0 → twc_cli-2.13.0}/twc/api/base.py +0 -0
- {twc_cli-2.11.0 → twc_cli-2.13.0}/twc/api/exceptions.py +0 -0
- {twc_cli-2.11.0 → twc_cli-2.13.0}/twc/api/types.py +0 -0
- {twc_cli-2.11.0 → twc_cli-2.13.0}/twc/apiwrap.py +0 -0
- {twc_cli-2.11.0 → twc_cli-2.13.0}/twc/commands/account.py +0 -0
- {twc_cli-2.11.0 → twc_cli-2.13.0}/twc/commands/common.py +0 -0
- {twc_cli-2.11.0 → twc_cli-2.13.0}/twc/commands/config.py +0 -0
- {twc_cli-2.11.0 → twc_cli-2.13.0}/twc/commands/database.py +0 -0
- {twc_cli-2.11.0 → twc_cli-2.13.0}/twc/commands/domain.py +0 -0
- {twc_cli-2.11.0 → twc_cli-2.13.0}/twc/commands/firewall.py +0 -0
- {twc_cli-2.11.0 → twc_cli-2.13.0}/twc/commands/floating_ip.py +0 -0
- {twc_cli-2.11.0 → twc_cli-2.13.0}/twc/commands/image.py +0 -0
- {twc_cli-2.11.0 → twc_cli-2.13.0}/twc/commands/kubernetes.py +0 -0
- {twc_cli-2.11.0 → twc_cli-2.13.0}/twc/commands/project.py +0 -0
- {twc_cli-2.11.0 → twc_cli-2.13.0}/twc/commands/server.py +0 -0
- {twc_cli-2.11.0 → twc_cli-2.13.0}/twc/commands/ssh_key.py +0 -0
- {twc_cli-2.11.0 → twc_cli-2.13.0}/twc/commands/vpc.py +0 -0
- {twc_cli-2.11.0 → twc_cli-2.13.0}/twc/fmt.py +0 -0
- {twc_cli-2.11.0 → twc_cli-2.13.0}/twc/typerx.py +0 -0
- {twc_cli-2.11.0 → twc_cli-2.13.0}/twc/utils.py +0 -0
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
В этом файле описаны все значимые изменения в Timeweb Cloud CLI. В выпусках мы придерживается правил [семантического версионирования](https://semver.org/lang/ru/).
|
|
4
4
|
|
|
5
|
+
# Версия 2.13.0 (2025.06.17)
|
|
6
|
+
|
|
7
|
+
## Добавлено
|
|
8
|
+
|
|
9
|
+
- Добавлена новая команда `twc apps` для управления сервисом Cloud Apps.
|
|
10
|
+
|
|
11
|
+
# Версия 2.12.0 (2025.04.11)
|
|
12
|
+
|
|
13
|
+
## Добавлено
|
|
14
|
+
|
|
15
|
+
- Добавлены опции `--max-connections`, `--connect-timeout`, `--client-timeout`, `--server-timeout` в команды `twc balancer create`, `twc balancer set`.
|
|
16
|
+
- В регионе `kz-1` для облачных серверов теперь доступен IPv6.
|
|
17
|
+
|
|
18
|
+
## Изменено
|
|
19
|
+
|
|
20
|
+
- Стандартный ендпоинт объектного хранилища изменён с `s3.timeweb.cloud` на `s3.twcstorage.ru`.
|
|
21
|
+
|
|
5
22
|
# Версия 2.11.0 (2025.04.01)
|
|
6
23
|
|
|
7
24
|
## Добавлено
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
2
|
Name: twc-cli
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.13.0
|
|
4
4
|
Summary: Timeweb Cloud Command Line Interface.
|
|
5
|
-
Home-page: https://github.com/timeweb-cloud/twc
|
|
6
5
|
License: MIT
|
|
7
6
|
Author: ge
|
|
8
7
|
Author-email: dev@timeweb.cloud
|
|
@@ -17,10 +16,11 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
17
16
|
Requires-Dist: colorama (>=0.4.6,<0.5.0)
|
|
18
17
|
Requires-Dist: pygments (>=2.18.0,<3.0.0)
|
|
19
18
|
Requires-Dist: pyyaml (>=6.0.1,<7.0.0)
|
|
20
|
-
Requires-Dist: requests (>=2.32.
|
|
19
|
+
Requires-Dist: requests (>=2.32.4,<3.0.0)
|
|
21
20
|
Requires-Dist: shellingham (>=1.5.4,<2.0.0)
|
|
22
21
|
Requires-Dist: toml (>=0.10.2,<0.11.0)
|
|
23
22
|
Requires-Dist: typer-slim (>=0.12.3,<0.13.0)
|
|
23
|
+
Project-URL: Homepage, https://github.com/timeweb-cloud/twc
|
|
24
24
|
Project-URL: Repository, https://github.com/timeweb-cloud/twc
|
|
25
25
|
Description-Content-Type: text/markdown
|
|
26
26
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "twc-cli"
|
|
3
|
-
version = "2.
|
|
3
|
+
version = "2.13.0"
|
|
4
4
|
description = "Timeweb Cloud Command Line Interface."
|
|
5
5
|
authors = ["ge <dev@timeweb.cloud>"]
|
|
6
6
|
homepage = "https://github.com/timeweb-cloud/twc"
|
|
@@ -12,7 +12,7 @@ packages = [{ include = "twc", from = "." }]
|
|
|
12
12
|
|
|
13
13
|
[tool.poetry.dependencies]
|
|
14
14
|
python = "^3.8.19"
|
|
15
|
-
requests = "^2.32.
|
|
15
|
+
requests = "^2.32.4"
|
|
16
16
|
typer-slim = "^0.12.3"
|
|
17
17
|
shellingham = "^1.5.4"
|
|
18
18
|
colorama = "^0.4.6"
|
|
@@ -22,6 +22,7 @@ from .commands import (
|
|
|
22
22
|
firewall,
|
|
23
23
|
floating_ip,
|
|
24
24
|
whoami,
|
|
25
|
+
apps,
|
|
25
26
|
)
|
|
26
27
|
from .commands.common import version_callback, version_option, verbose_option
|
|
27
28
|
|
|
@@ -33,6 +34,7 @@ cli = TyperAlias(
|
|
|
33
34
|
)
|
|
34
35
|
cli.add_typer(config, name="config")
|
|
35
36
|
cli.add_typer(account, name="account")
|
|
37
|
+
cli.add_typer(apps, name="apps")
|
|
36
38
|
cli.add_typer(server, name="server", aliases=["servers", "s"])
|
|
37
39
|
cli.add_typer(ssh_key, name="ssh-key", aliases=["ssh-keys", "k"])
|
|
38
40
|
cli.add_typer(image, name="image", aliases=["images", "i"])
|
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
from ipaddress import IPv4Address, IPv6Address, IPv4Network, IPv6Network
|
|
5
6
|
from typing import Optional, Union, List
|
|
6
|
-
from uuid import UUID
|
|
7
7
|
from pathlib import Path
|
|
8
|
-
from
|
|
8
|
+
from uuid import UUID
|
|
9
|
+
|
|
10
|
+
import yaml
|
|
9
11
|
|
|
10
12
|
from .base import TimewebCloudBase
|
|
11
13
|
from .types import (
|
|
@@ -34,6 +36,43 @@ from .types import (
|
|
|
34
36
|
class TimewebCloud(TimewebCloudBase):
|
|
35
37
|
"""Timeweb Cloud API client class."""
|
|
36
38
|
|
|
39
|
+
# -----------------------------------------------------------------------
|
|
40
|
+
# Apps
|
|
41
|
+
|
|
42
|
+
def get_apps(self):
|
|
43
|
+
"""Return Timeweb Cloud apps list."""
|
|
44
|
+
return self._request("GET", f"{self.api_url}/apps")
|
|
45
|
+
|
|
46
|
+
def create_app(self, yaml_config_path: str):
|
|
47
|
+
"""Create Timeweb Cloud app."""
|
|
48
|
+
|
|
49
|
+
with open(yaml_config_path, "r", encoding="utf-8") as f:
|
|
50
|
+
payload = yaml.safe_load(f)
|
|
51
|
+
payload = payload["app"]
|
|
52
|
+
return self._request("POST", f"{self.api_url}/apps", json=payload)
|
|
53
|
+
|
|
54
|
+
def get_app(self, app_id: int):
|
|
55
|
+
"""Return Timeweb Cloud app."""
|
|
56
|
+
return self._request("GET", f"{self.api_url}/apps/{app_id}")
|
|
57
|
+
|
|
58
|
+
def get_vcs_providers(self):
|
|
59
|
+
"""Return Timeweb Cloud vcs providers."""
|
|
60
|
+
return self._request("GET", f"{self.api_url}/vcs-provider")
|
|
61
|
+
|
|
62
|
+
def get_repositories(self, vcs_provider_id: str):
|
|
63
|
+
"""Return Timeweb Cloud user vcs repositories."""
|
|
64
|
+
return self._request(
|
|
65
|
+
"GET", f"{self.api_url}/vcs-provider/{vcs_provider_id}"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
def get_apps_tarifs(self):
|
|
69
|
+
"""Return Timeweb Cloud Apps tarifs"""
|
|
70
|
+
return self._request("GET", f"{self.api_url}/presets/apps")
|
|
71
|
+
|
|
72
|
+
def delete_app(self, app_id: int):
|
|
73
|
+
"""Delete Timeweb Cloud app."""
|
|
74
|
+
return self._request("DELETE", f"{self.api_url}/apps/{app_id}")
|
|
75
|
+
|
|
37
76
|
# -----------------------------------------------------------------------
|
|
38
77
|
# Account
|
|
39
78
|
|
|
@@ -1162,6 +1201,11 @@ class TimewebCloud(TimewebCloudBase):
|
|
|
1162
1201
|
comment: Optional[str] = None,
|
|
1163
1202
|
project_id: Optional[int] = None,
|
|
1164
1203
|
certificates: Optional[dict] = None,
|
|
1204
|
+
max_connections: Optional[int] = None,
|
|
1205
|
+
connect_timeout: Optional[int] = None,
|
|
1206
|
+
client_timeout: Optional[int] = None,
|
|
1207
|
+
server_timeout: Optional[int] = None,
|
|
1208
|
+
http_timeout: Optional[int] = None,
|
|
1165
1209
|
):
|
|
1166
1210
|
"""Create load balancer."""
|
|
1167
1211
|
payload = {
|
|
@@ -1183,6 +1227,31 @@ class TimewebCloud(TimewebCloudBase):
|
|
|
1183
1227
|
**({"comment": comment} if comment else {}),
|
|
1184
1228
|
**({"project_id": project_id} if project_id else {}),
|
|
1185
1229
|
**({"certificates": certificates} if certificates else {}),
|
|
1230
|
+
**(
|
|
1231
|
+
{"maxconn": max_connections}
|
|
1232
|
+
if max_connections is not None
|
|
1233
|
+
else {}
|
|
1234
|
+
),
|
|
1235
|
+
**(
|
|
1236
|
+
{"connect_timeout": connect_timeout}
|
|
1237
|
+
if connect_timeout is not None
|
|
1238
|
+
else {}
|
|
1239
|
+
),
|
|
1240
|
+
**(
|
|
1241
|
+
{"client_timeout": client_timeout}
|
|
1242
|
+
if client_timeout is not None
|
|
1243
|
+
else {}
|
|
1244
|
+
),
|
|
1245
|
+
**(
|
|
1246
|
+
{"server_timeout": server_timeout}
|
|
1247
|
+
if server_timeout is not None
|
|
1248
|
+
else {}
|
|
1249
|
+
),
|
|
1250
|
+
**(
|
|
1251
|
+
{"httprequest_timeout": http_timeout}
|
|
1252
|
+
if http_timeout is not None
|
|
1253
|
+
else {}
|
|
1254
|
+
),
|
|
1186
1255
|
}
|
|
1187
1256
|
return self._request("POST", f"{self.api_url}/balancers", json=payload)
|
|
1188
1257
|
|
|
@@ -1203,6 +1272,11 @@ class TimewebCloud(TimewebCloudBase):
|
|
|
1203
1272
|
proxy_protocol: Optional[bool] = None,
|
|
1204
1273
|
force_https: Optional[bool] = None,
|
|
1205
1274
|
backend_keepalive: Optional[bool] = None,
|
|
1275
|
+
max_connections: Optional[int] = None,
|
|
1276
|
+
connect_timeout: Optional[int] = None,
|
|
1277
|
+
client_timeout: Optional[int] = None,
|
|
1278
|
+
server_timeout: Optional[int] = None,
|
|
1279
|
+
http_timeout: Optional[int] = None,
|
|
1206
1280
|
):
|
|
1207
1281
|
"""Update load balancer settings."""
|
|
1208
1282
|
payload = {
|
|
@@ -1228,6 +1302,31 @@ class TimewebCloud(TimewebCloudBase):
|
|
|
1228
1302
|
if backend_keepalive is not None
|
|
1229
1303
|
else {}
|
|
1230
1304
|
),
|
|
1305
|
+
**(
|
|
1306
|
+
{"maxconn": max_connections}
|
|
1307
|
+
if max_connections is not None
|
|
1308
|
+
else {}
|
|
1309
|
+
),
|
|
1310
|
+
**(
|
|
1311
|
+
{"connect_timeout": connect_timeout}
|
|
1312
|
+
if connect_timeout is not None
|
|
1313
|
+
else {}
|
|
1314
|
+
),
|
|
1315
|
+
**(
|
|
1316
|
+
{"client_timeout": client_timeout}
|
|
1317
|
+
if client_timeout is not None
|
|
1318
|
+
else {}
|
|
1319
|
+
),
|
|
1320
|
+
**(
|
|
1321
|
+
{"server_timeout": server_timeout}
|
|
1322
|
+
if server_timeout is not None
|
|
1323
|
+
else {}
|
|
1324
|
+
),
|
|
1325
|
+
**(
|
|
1326
|
+
{"httprequest_timeout": http_timeout}
|
|
1327
|
+
if http_timeout is not None
|
|
1328
|
+
else {}
|
|
1329
|
+
),
|
|
1231
1330
|
}
|
|
1232
1331
|
return self._request(
|
|
1233
1332
|
"PATCH", f"{self.api_url}/balancers/{balancer_id}", json=payload
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
"""Manage apps."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
from requests import Response
|
|
8
|
+
|
|
9
|
+
from twc import fmt
|
|
10
|
+
from twc.typerx import TyperAlias
|
|
11
|
+
from twc.apiwrap import create_client
|
|
12
|
+
from .common import (
|
|
13
|
+
verbose_option,
|
|
14
|
+
config_option,
|
|
15
|
+
profile_option,
|
|
16
|
+
filter_option,
|
|
17
|
+
output_format_option,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
apps = TyperAlias(help=__doc__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# ------------------------------------------------------------- #
|
|
25
|
+
# $ twc apps list #
|
|
26
|
+
# ------------------------------------------------------------- #
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def print_apps(response: Response, filters: Optional[str]):
|
|
30
|
+
"""Print table with apps list."""
|
|
31
|
+
# pylint: disable=invalid-name
|
|
32
|
+
_apps = response.json()["apps"]
|
|
33
|
+
if filters:
|
|
34
|
+
_apps = fmt.filter_list(_apps, filters)
|
|
35
|
+
table = fmt.Table()
|
|
36
|
+
table.header(
|
|
37
|
+
[
|
|
38
|
+
"ID",
|
|
39
|
+
"NAME",
|
|
40
|
+
"STATUS",
|
|
41
|
+
"TYPE",
|
|
42
|
+
"IPV4",
|
|
43
|
+
]
|
|
44
|
+
)
|
|
45
|
+
for app in _apps:
|
|
46
|
+
table.row(
|
|
47
|
+
[
|
|
48
|
+
app["id"],
|
|
49
|
+
app["name"],
|
|
50
|
+
app["status"],
|
|
51
|
+
app["type"],
|
|
52
|
+
app["ip"],
|
|
53
|
+
]
|
|
54
|
+
)
|
|
55
|
+
table.print()
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@apps.command("list", "ls")
|
|
59
|
+
def apps_list(
|
|
60
|
+
verbose: Optional[bool] = verbose_option,
|
|
61
|
+
config: Optional[Path] = config_option,
|
|
62
|
+
profile: Optional[str] = profile_option,
|
|
63
|
+
output_format: Optional[str] = output_format_option,
|
|
64
|
+
filters: Optional[str] = filter_option,
|
|
65
|
+
):
|
|
66
|
+
"""List apps."""
|
|
67
|
+
client = create_client(config, profile)
|
|
68
|
+
response = client.get_apps()
|
|
69
|
+
fmt.printer(
|
|
70
|
+
response,
|
|
71
|
+
output_format=output_format,
|
|
72
|
+
filters=filters,
|
|
73
|
+
func=print_apps,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@apps.command("create")
|
|
78
|
+
def app_create(
|
|
79
|
+
yml_config_path: str,
|
|
80
|
+
verbose: Optional[bool] = verbose_option,
|
|
81
|
+
config: Optional[Path] = config_option,
|
|
82
|
+
profile: Optional[str] = profile_option,
|
|
83
|
+
output_format: Optional[str] = output_format_option,
|
|
84
|
+
status: Optional[bool] = typer.Option(
|
|
85
|
+
False,
|
|
86
|
+
"--status",
|
|
87
|
+
help="Display status and exit with 0 if status is 'started'.",
|
|
88
|
+
),
|
|
89
|
+
):
|
|
90
|
+
"""Create app"""
|
|
91
|
+
client = create_client(config, profile)
|
|
92
|
+
response = client.create_app(yml_config_path)
|
|
93
|
+
fmt.printer(
|
|
94
|
+
response,
|
|
95
|
+
output_format=output_format,
|
|
96
|
+
func=lambda response: print(response.json()["app"]["id"]),
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def print_vcs_providers(response: Response):
|
|
101
|
+
"""Print table with vcs list."""
|
|
102
|
+
# pylint: disable=invalid-name
|
|
103
|
+
providers = response.json()["providers"]
|
|
104
|
+
table = fmt.Table()
|
|
105
|
+
table.header(
|
|
106
|
+
[
|
|
107
|
+
"LOGIN",
|
|
108
|
+
"PROVIDER",
|
|
109
|
+
"PROVIDER_ID",
|
|
110
|
+
]
|
|
111
|
+
)
|
|
112
|
+
for provider in providers:
|
|
113
|
+
table.row(
|
|
114
|
+
[
|
|
115
|
+
provider["login"],
|
|
116
|
+
provider["provider"],
|
|
117
|
+
provider["provider_id"],
|
|
118
|
+
]
|
|
119
|
+
)
|
|
120
|
+
table.print()
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@apps.command("get-vcs-providers")
|
|
124
|
+
def app_get_vcs_providers(
|
|
125
|
+
verbose: Optional[bool] = verbose_option,
|
|
126
|
+
config: Optional[Path] = config_option,
|
|
127
|
+
profile: Optional[str] = profile_option,
|
|
128
|
+
output_format: Optional[str] = output_format_option,
|
|
129
|
+
):
|
|
130
|
+
"""Get VCS providers."""
|
|
131
|
+
client = create_client(config, profile)
|
|
132
|
+
response = client.get_vcs_providers()
|
|
133
|
+
fmt.printer(
|
|
134
|
+
response, output_format=output_format, func=print_vcs_providers
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def print_vcs_repositories(response: Response):
|
|
139
|
+
"""Print table with vcs list."""
|
|
140
|
+
# pylint: disable=invalid-name
|
|
141
|
+
providers = response.json()["repositories"]
|
|
142
|
+
table = fmt.Table()
|
|
143
|
+
table.header(["FULL NAME", "ID", "NAME", "URL", "IS PRIVATE"])
|
|
144
|
+
for provider in providers:
|
|
145
|
+
table.row(
|
|
146
|
+
[
|
|
147
|
+
provider["full_name"],
|
|
148
|
+
provider["id"],
|
|
149
|
+
provider["name"],
|
|
150
|
+
provider["url"],
|
|
151
|
+
provider["is_private"],
|
|
152
|
+
]
|
|
153
|
+
)
|
|
154
|
+
table.print()
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
@apps.command("get-repositories")
|
|
158
|
+
def app_get_repositories(
|
|
159
|
+
vcs_provider_id: str,
|
|
160
|
+
verbose: Optional[bool] = verbose_option,
|
|
161
|
+
config: Optional[Path] = config_option,
|
|
162
|
+
profile: Optional[str] = profile_option,
|
|
163
|
+
output_format: Optional[str] = output_format_option,
|
|
164
|
+
):
|
|
165
|
+
"""Get repositories."""
|
|
166
|
+
client = create_client(config, profile)
|
|
167
|
+
response = client.get_repositories(vcs_provider_id)
|
|
168
|
+
fmt.printer(
|
|
169
|
+
response, output_format=output_format, func=print_vcs_repositories
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def print_apps_tarifs(response: Response, typ: str):
|
|
174
|
+
"""Print Timeweb Cloud Apps tarifs."""
|
|
175
|
+
# pylint: disable=invalid-name
|
|
176
|
+
if typ == "backend":
|
|
177
|
+
key = "backend_presets"
|
|
178
|
+
elif typ == "frontend":
|
|
179
|
+
key = "frontend_presets"
|
|
180
|
+
else:
|
|
181
|
+
raise KeyError("Tarifs can be only backend or frontend")
|
|
182
|
+
|
|
183
|
+
providers = response.json()[key]
|
|
184
|
+
table = fmt.Table()
|
|
185
|
+
if key == "backend_presets":
|
|
186
|
+
table.header(
|
|
187
|
+
[
|
|
188
|
+
"CPU",
|
|
189
|
+
"CPU FREQUENCY",
|
|
190
|
+
"DESCRIPTION",
|
|
191
|
+
"DESCRIPTION SHORT",
|
|
192
|
+
"DISK",
|
|
193
|
+
"ID",
|
|
194
|
+
"LOCATION",
|
|
195
|
+
"PRICE",
|
|
196
|
+
"RAM",
|
|
197
|
+
]
|
|
198
|
+
)
|
|
199
|
+
for provider in providers:
|
|
200
|
+
table.row(
|
|
201
|
+
[
|
|
202
|
+
provider["cpu"],
|
|
203
|
+
provider["cpu_frequency"],
|
|
204
|
+
provider["description"],
|
|
205
|
+
provider["description_short"],
|
|
206
|
+
provider["disk"],
|
|
207
|
+
provider["id"],
|
|
208
|
+
provider["location"],
|
|
209
|
+
provider["price"],
|
|
210
|
+
provider["ram"],
|
|
211
|
+
]
|
|
212
|
+
)
|
|
213
|
+
elif key == "frontend_presets":
|
|
214
|
+
table.header(
|
|
215
|
+
[
|
|
216
|
+
"DESCRIPTION",
|
|
217
|
+
"DESCRIPTION_SHORT",
|
|
218
|
+
"DISK",
|
|
219
|
+
"ID",
|
|
220
|
+
"LOCATION",
|
|
221
|
+
"PRICE",
|
|
222
|
+
"REQUESTS",
|
|
223
|
+
]
|
|
224
|
+
)
|
|
225
|
+
for provider in providers:
|
|
226
|
+
table.row(
|
|
227
|
+
[
|
|
228
|
+
provider["description"],
|
|
229
|
+
provider["description_short"],
|
|
230
|
+
provider["disk"],
|
|
231
|
+
provider["id"],
|
|
232
|
+
provider["location"],
|
|
233
|
+
provider["price"],
|
|
234
|
+
provider["requests"],
|
|
235
|
+
]
|
|
236
|
+
)
|
|
237
|
+
table.print()
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
@apps.command("list-presets")
|
|
241
|
+
def get_apps_tarifs(
|
|
242
|
+
_type: str = typer.Argument(..., metavar="TYPE"),
|
|
243
|
+
verbose: Optional[bool] = verbose_option,
|
|
244
|
+
config: Optional[Path] = config_option,
|
|
245
|
+
profile: Optional[str] = profile_option,
|
|
246
|
+
output_format: Optional[str] = output_format_option,
|
|
247
|
+
):
|
|
248
|
+
"""Get tarifs; backend or frontend"""
|
|
249
|
+
client = create_client(config, profile)
|
|
250
|
+
response = client.get_apps_tarifs()
|
|
251
|
+
fmt.printer(
|
|
252
|
+
response,
|
|
253
|
+
output_format=output_format,
|
|
254
|
+
typ=_type,
|
|
255
|
+
func=print_apps_tarifs,
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def print_app_delete_response(response: Response, app_id: int):
|
|
260
|
+
"""Print table with apps list."""
|
|
261
|
+
# pylint: disable=invalid-name
|
|
262
|
+
table = fmt.Table()
|
|
263
|
+
table.header(
|
|
264
|
+
[
|
|
265
|
+
"ID",
|
|
266
|
+
]
|
|
267
|
+
)
|
|
268
|
+
table.row(
|
|
269
|
+
[
|
|
270
|
+
app_id,
|
|
271
|
+
]
|
|
272
|
+
)
|
|
273
|
+
table.print()
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
@apps.command("delete")
|
|
277
|
+
def delete_app(
|
|
278
|
+
app_id: int,
|
|
279
|
+
verbose: Optional[bool] = verbose_option,
|
|
280
|
+
config: Optional[Path] = config_option,
|
|
281
|
+
profile: Optional[str] = profile_option,
|
|
282
|
+
output_format: Optional[str] = output_format_option,
|
|
283
|
+
):
|
|
284
|
+
"""Delete apps."""
|
|
285
|
+
client = create_client(config, profile)
|
|
286
|
+
response = client.delete_app(app_id)
|
|
287
|
+
fmt.printer(
|
|
288
|
+
response,
|
|
289
|
+
output_format=output_format,
|
|
290
|
+
app_id=app_id,
|
|
291
|
+
func=print_app_delete_response,
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def get_app(response: Response):
|
|
296
|
+
"""Print table with apps list."""
|
|
297
|
+
# pylint: disable=invalid-name
|
|
298
|
+
app = response.json()["app"]
|
|
299
|
+
table = fmt.Table()
|
|
300
|
+
table.header(
|
|
301
|
+
[
|
|
302
|
+
"ID",
|
|
303
|
+
"LOCATION",
|
|
304
|
+
"STATUS",
|
|
305
|
+
"TYPE",
|
|
306
|
+
"IPV4",
|
|
307
|
+
]
|
|
308
|
+
)
|
|
309
|
+
table.row(
|
|
310
|
+
[
|
|
311
|
+
app["id"],
|
|
312
|
+
app["location"],
|
|
313
|
+
app["status"],
|
|
314
|
+
app["type"],
|
|
315
|
+
app["ip"],
|
|
316
|
+
]
|
|
317
|
+
)
|
|
318
|
+
table.print()
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
@apps.command("get")
|
|
322
|
+
def app_get(
|
|
323
|
+
app_id: int,
|
|
324
|
+
verbose: Optional[bool] = verbose_option,
|
|
325
|
+
config: Optional[Path] = config_option,
|
|
326
|
+
profile: Optional[str] = profile_option,
|
|
327
|
+
output_format: Optional[str] = output_format_option,
|
|
328
|
+
status: Optional[bool] = typer.Option(
|
|
329
|
+
False,
|
|
330
|
+
"--status",
|
|
331
|
+
help="Display status and exit with 0 if status is 'started'.",
|
|
332
|
+
),
|
|
333
|
+
):
|
|
334
|
+
"""Get database info."""
|
|
335
|
+
client = create_client(config, profile)
|
|
336
|
+
response = client.get_app(app_id)
|
|
337
|
+
fmt.printer(response, output_format=output_format, func=get_app)
|
|
@@ -198,6 +198,22 @@ def balancer_create(
|
|
|
198
198
|
proxy_protocol: bool = typer.Option(False),
|
|
199
199
|
force_https: bool = typer.Option(False),
|
|
200
200
|
backend_keepalive: bool = typer.Option(False),
|
|
201
|
+
max_connections: Optional[int] = typer.Option(
|
|
202
|
+
None, help="Backend server's maximum number of concurrent connections."
|
|
203
|
+
),
|
|
204
|
+
connect_timeout: Optional[int] = typer.Option(
|
|
205
|
+
None,
|
|
206
|
+
help="Maximum time to wait for a connection attempt to a backend server to succeed.",
|
|
207
|
+
),
|
|
208
|
+
client_timeout: Optional[int] = typer.Option(
|
|
209
|
+
None, help="Maximum inactivity time on the client side."
|
|
210
|
+
),
|
|
211
|
+
server_timeout: Optional[int] = typer.Option(
|
|
212
|
+
None, help="Maximum time for pending data staying into output buffer."
|
|
213
|
+
),
|
|
214
|
+
http_timeout: Optional[int] = typer.Option(
|
|
215
|
+
None, help="Maximum allowed time to wait for a complete HTTP request."
|
|
216
|
+
),
|
|
201
217
|
project_id: Optional[int] = typer.Option(
|
|
202
218
|
None,
|
|
203
219
|
envvar="TWC_PROJECT",
|
|
@@ -259,6 +275,11 @@ def balancer_create(
|
|
|
259
275
|
"network": {},
|
|
260
276
|
"project_id": project_id,
|
|
261
277
|
"certificates": {},
|
|
278
|
+
"max_connections": max_connections,
|
|
279
|
+
"connect_timeout": connect_timeout,
|
|
280
|
+
"client_timeout": client_timeout,
|
|
281
|
+
"server_timeout": server_timeout,
|
|
282
|
+
"http_timeout": http_timeout,
|
|
262
283
|
}
|
|
263
284
|
|
|
264
285
|
if cert_type == CertType.CUSTOM:
|
|
@@ -412,6 +433,22 @@ def balancer_set(
|
|
|
412
433
|
proxy_protocol: Optional[bool] = typer.Option(None),
|
|
413
434
|
force_https: Optional[bool] = typer.Option(None),
|
|
414
435
|
backend_keepalive: Optional[bool] = typer.Option(None),
|
|
436
|
+
max_connections: Optional[int] = typer.Option(
|
|
437
|
+
None, help="Backend server's maximum number of concurrent connections."
|
|
438
|
+
),
|
|
439
|
+
connect_timeout: Optional[int] = typer.Option(
|
|
440
|
+
None,
|
|
441
|
+
help="Maximum time to wait for a connection attempt to a backend server to succeed.",
|
|
442
|
+
),
|
|
443
|
+
client_timeout: Optional[int] = typer.Option(
|
|
444
|
+
None, help="Maximum inactivity time on the client side."
|
|
445
|
+
),
|
|
446
|
+
server_timeout: Optional[int] = typer.Option(
|
|
447
|
+
None, help="Maximum time for pending data staying into output buffer."
|
|
448
|
+
),
|
|
449
|
+
http_timeout: Optional[int] = typer.Option(
|
|
450
|
+
None, help="Maximum allowed time to wait for a complete HTTP request."
|
|
451
|
+
),
|
|
415
452
|
):
|
|
416
453
|
"""Change load balancer parameters."""
|
|
417
454
|
client = create_client(config, profile)
|
|
@@ -442,6 +479,11 @@ def balancer_set(
|
|
|
442
479
|
proxy_protocol=proxy_protocol,
|
|
443
480
|
force_https=force_https,
|
|
444
481
|
backend_keepalive=backend_keepalive,
|
|
482
|
+
max_connections=max_connections,
|
|
483
|
+
connect_timeout=connect_timeout,
|
|
484
|
+
client_timeout=client_timeout,
|
|
485
|
+
server_timeout=server_timeout,
|
|
486
|
+
http_timeout=http_timeout,
|
|
445
487
|
)
|
|
446
488
|
|
|
447
489
|
fmt.printer(
|
|
@@ -611,7 +611,7 @@ def storage_genconfig(
|
|
|
611
611
|
"rclone": RCLONE_CONFIG_TEMPLATE.strip(),
|
|
612
612
|
}
|
|
613
613
|
|
|
614
|
-
endpoint = "s3.
|
|
614
|
+
endpoint = "s3.twcstorage.ru"
|
|
615
615
|
if not access_key.isupper():
|
|
616
616
|
# Legacy object storage service have lowercase usernames only.
|
|
617
617
|
# New storage, on the contrary, always has keys in uppercase.
|
|
@@ -8,7 +8,7 @@ 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", "ru-3", "pl-1", "nl-1"]
|
|
11
|
+
REGIONS_WITH_IPV6 = ["ru-1", "ru-3", "pl-1", "nl-1", "kz-1"]
|
|
12
12
|
REGIONS_WITH_IMAGES = ["ru-1", "ru-3", "kz-1", "pl-1", "nl-1"]
|
|
13
13
|
REGIONS_WITH_LAN = ["ru-1", "ru-3", "nl-1", "pl-1", "de-1"]
|
|
14
14
|
ZONES_WITH_LAN = [
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|