twc-cli 2.3.0__tar.gz → 2.4.1__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.3.0 → twc_cli-2.4.1}/CHANGELOG.md +24 -0
- {twc_cli-2.3.0 → twc_cli-2.4.1}/PKG-INFO +1 -1
- {twc_cli-2.3.0 → twc_cli-2.4.1}/pyproject.toml +1 -1
- {twc_cli-2.3.0 → twc_cli-2.4.1}/twc/__main__.py +11 -1
- {twc_cli-2.3.0 → twc_cli-2.4.1}/twc/__version__.py +1 -1
- {twc_cli-2.3.0 → twc_cli-2.4.1}/twc/api/base.py +3 -2
- {twc_cli-2.3.0 → twc_cli-2.4.1}/twc/api/client.py +349 -6
- {twc_cli-2.3.0 → twc_cli-2.4.1}/twc/api/types.py +28 -2
- {twc_cli-2.3.0 → twc_cli-2.4.1}/twc/commands/__init__.py +3 -0
- {twc_cli-2.3.0 → twc_cli-2.4.1}/twc/commands/account.py +30 -40
- {twc_cli-2.3.0 → twc_cli-2.4.1}/twc/commands/balancer.py +38 -36
- {twc_cli-2.3.0 → twc_cli-2.4.1}/twc/commands/common.py +8 -4
- {twc_cli-2.3.0 → twc_cli-2.4.1}/twc/commands/config.py +28 -1
- twc_cli-2.4.1/twc/commands/domain.py +519 -0
- twc_cli-2.4.1/twc/commands/firewall.py +654 -0
- {twc_cli-2.3.0 → twc_cli-2.4.1}/twc/commands/image.py +14 -25
- {twc_cli-2.3.0 → twc_cli-2.4.1}/twc/commands/project.py +2 -2
- {twc_cli-2.3.0 → twc_cli-2.4.1}/twc/commands/server.py +16 -5
- twc_cli-2.4.1/twc/commands/vpc.py +293 -0
- {twc_cli-2.3.0 → twc_cli-2.4.1}/twc/fmt.py +15 -0
- {twc_cli-2.3.0 → twc_cli-2.4.1}/twc/typerx.py +7 -2
- {twc_cli-2.3.0 → twc_cli-2.4.1}/twc/vars.py +3 -2
- {twc_cli-2.3.0 → twc_cli-2.4.1}/COPYING +0 -0
- {twc_cli-2.3.0 → twc_cli-2.4.1}/README.md +0 -0
- {twc_cli-2.3.0 → twc_cli-2.4.1}/twc/__init__.py +0 -0
- {twc_cli-2.3.0 → twc_cli-2.4.1}/twc/api/__init__.py +0 -0
- {twc_cli-2.3.0 → twc_cli-2.4.1}/twc/api/exceptions.py +0 -0
- {twc_cli-2.3.0 → twc_cli-2.4.1}/twc/apiwrap.py +0 -0
- {twc_cli-2.3.0 → twc_cli-2.4.1}/twc/commands/database.py +0 -0
- {twc_cli-2.3.0 → twc_cli-2.4.1}/twc/commands/kubernetes.py +0 -0
- {twc_cli-2.3.0 → twc_cli-2.4.1}/twc/commands/ssh_key.py +0 -0
- {twc_cli-2.3.0 → twc_cli-2.4.1}/twc/commands/storage.py +0 -0
- {twc_cli-2.3.0 → twc_cli-2.4.1}/twc/utils.py +0 -0
|
@@ -2,6 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
В этом файле описаны все значимые изменения в Timeweb Cloud CLI. В выпусках мы придерживается правил [семантического версионирования](https://semver.org/lang/ru/).
|
|
4
4
|
|
|
5
|
+
# Версия 2.4.0 (2023.08.21)
|
|
6
|
+
|
|
7
|
+
## Добавлено
|
|
8
|
+
|
|
9
|
+
- Добавлена команда `twc vpc` (`twc network`) для работы с сервисом VPC. Доступны все предоставляемые API операции с приватными сетями.
|
|
10
|
+
- В команды для создания облачных серверов и балансировщиков нагрузки добавлена опция `--network`, с помощью которой можно задать ID приватной сети.
|
|
11
|
+
- Добавлена команда `twc domain` для управления доменными именами (добавление, удаление) и DNS-записями. Спасибо @ranmasootome за вклад!
|
|
12
|
+
- Добавлена команда `twc firewall` для управления облачным файрволом.
|
|
13
|
+
- Добавлена команда `twc config profiles` для вывода списка профилей из конфигурационного файла.
|
|
14
|
+
|
|
15
|
+
## Изменено
|
|
16
|
+
|
|
17
|
+
- Удалена опция `--with-deleted` у команды `twc image list`.
|
|
18
|
+
- Изменён вывод команд `twc image list` и `twc image get`.
|
|
19
|
+
- Опция `--local-network` (`twc server create`) объявлена устаревшей и скрыта. Используйте `--network` вместо неё.
|
|
20
|
+
- Вызов команды без аргументов приведёт к показу текста справки вместо ошибки.
|
|
21
|
+
- В дополнение к `--help` добавлена опция `-h` для вызова справки.
|
|
22
|
+
- Мелкие улучшения кода.
|
|
23
|
+
|
|
24
|
+
## Исправлено
|
|
25
|
+
|
|
26
|
+
- Исправлена ошибка в API-клиенте в методе удаления балансировщиков нагрузки.
|
|
27
|
+
- Другие мелкие исправления.
|
|
28
|
+
|
|
5
29
|
# Версия 2.3.0 (2023.06.28)
|
|
6
30
|
|
|
7
31
|
## Добавлено
|
|
@@ -17,11 +17,18 @@ from .commands import (
|
|
|
17
17
|
storage,
|
|
18
18
|
balancer,
|
|
19
19
|
cluster,
|
|
20
|
+
domain,
|
|
21
|
+
vpc,
|
|
22
|
+
firewall,
|
|
20
23
|
)
|
|
21
24
|
from .commands.common import version_callback, version_option, verbose_option
|
|
22
25
|
|
|
23
26
|
|
|
24
|
-
cli = TyperAlias(
|
|
27
|
+
cli = TyperAlias(
|
|
28
|
+
help=__doc__,
|
|
29
|
+
no_args_is_help=True,
|
|
30
|
+
context_settings={"help_option_names": ["-h", "--help"]},
|
|
31
|
+
)
|
|
25
32
|
cli.add_typer(config, name="config")
|
|
26
33
|
cli.add_typer(account, name="account")
|
|
27
34
|
cli.add_typer(server, name="server", aliases=["servers", "s"])
|
|
@@ -34,6 +41,9 @@ cli.add_typer(balancer, name="balancer", aliases=["balancers", "lb"])
|
|
|
34
41
|
cli.add_typer(
|
|
35
42
|
cluster, name="cluster", aliases=["clusters", "kubernetes", "k8s"]
|
|
36
43
|
)
|
|
44
|
+
cli.add_typer(domain, name="domain", aliases=["domains", "d"])
|
|
45
|
+
cli.add_typer(vpc, name="vpc", aliases=["vpcs", "network", "networks"])
|
|
46
|
+
cli.add_typer(firewall, name="firewall", aliases=["fw"])
|
|
37
47
|
|
|
38
48
|
|
|
39
49
|
@cli.command("version")
|
|
@@ -17,7 +17,6 @@ class TimewebCloudBase:
|
|
|
17
17
|
# pylint: disable=invalid-name
|
|
18
18
|
|
|
19
19
|
API_BASE_URL = "https://api.timeweb.cloud"
|
|
20
|
-
API_PATH = "/api/v1"
|
|
21
20
|
TIMEOUT = 100
|
|
22
21
|
USER_AGENT = f"TWC-CLI/{__version__} Python {__pyversion__}"
|
|
23
22
|
|
|
@@ -25,7 +24,7 @@ class TimewebCloudBase:
|
|
|
25
24
|
self,
|
|
26
25
|
api_token: str,
|
|
27
26
|
api_base_url: Optional[str] = API_BASE_URL,
|
|
28
|
-
api_path: Optional[str] =
|
|
27
|
+
api_path: Optional[str] = "/api/v1",
|
|
29
28
|
headers: Optional[dict] = None,
|
|
30
29
|
user_agent: Optional[str] = USER_AGENT,
|
|
31
30
|
timeout: Optional[int] = TIMEOUT,
|
|
@@ -37,6 +36,8 @@ class TimewebCloudBase:
|
|
|
37
36
|
self.api_base_url = api_base_url
|
|
38
37
|
self.api_path = api_path
|
|
39
38
|
self.api_url = self.api_base_url + self.api_path
|
|
39
|
+
self.api_url_v1 = self.api_base_url + "/api/v1"
|
|
40
|
+
self.api_url_v2 = self.api_base_url + "/api/v2"
|
|
40
41
|
self.timeout = timeout
|
|
41
42
|
self.headers = requests.utils.default_headers()
|
|
42
43
|
self.headers["User-Agent"] = user_agent
|
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
from typing import Optional, Union, List
|
|
4
4
|
from uuid import UUID
|
|
5
5
|
from pathlib import Path
|
|
6
|
-
from ipaddress import IPv4Address, IPv6Address
|
|
6
|
+
from ipaddress import IPv4Address, IPv6Address, IPv4Network, IPv6Network
|
|
7
7
|
|
|
8
8
|
from .base import TimewebCloudBase
|
|
9
9
|
from .types import (
|
|
10
|
+
DNSRecordType,
|
|
10
11
|
ServerConfiguration,
|
|
11
12
|
ServerAction,
|
|
12
13
|
ServerLogOrder,
|
|
@@ -17,11 +18,13 @@ from .types import (
|
|
|
17
18
|
BackupInterval,
|
|
18
19
|
IPVersion,
|
|
19
20
|
ServiceRegion,
|
|
20
|
-
|
|
21
|
+
ResourceType,
|
|
21
22
|
DBMS,
|
|
22
23
|
MySQLAuthPlugin,
|
|
23
24
|
LoadBalancerProto,
|
|
24
25
|
LoadBalancerAlgo,
|
|
26
|
+
FirewallProto,
|
|
27
|
+
FirewallDirection,
|
|
25
28
|
)
|
|
26
29
|
|
|
27
30
|
|
|
@@ -67,8 +70,9 @@ class TimewebCloud(TimewebCloudBase):
|
|
|
67
70
|
avatar_id: Optional[str] = None,
|
|
68
71
|
software_id: Optional[int] = None,
|
|
69
72
|
ssh_keys_ids: Optional[List[int]] = None,
|
|
70
|
-
is_local_network: bool =
|
|
73
|
+
is_local_network: Optional[bool] = None,
|
|
71
74
|
is_ddos_guard: bool = False,
|
|
75
|
+
network: Optional[dict] = None,
|
|
72
76
|
):
|
|
73
77
|
"""Create new Cloud Server. Note:
|
|
74
78
|
|
|
@@ -91,7 +95,12 @@ class TimewebCloud(TimewebCloudBase):
|
|
|
91
95
|
**({"software_id": software_id} if software_id else {}),
|
|
92
96
|
**({"ssh_keys_ids": ssh_keys_ids} if ssh_keys_ids else {}),
|
|
93
97
|
"is_ddos_guard": is_ddos_guard,
|
|
94
|
-
|
|
98
|
+
**(
|
|
99
|
+
{"is_local_network": is_local_network}
|
|
100
|
+
if is_local_network is not None
|
|
101
|
+
else {}
|
|
102
|
+
),
|
|
103
|
+
**({"network": network} if network else {}),
|
|
95
104
|
**({"configuration": configuration} if configuration else {}),
|
|
96
105
|
**({"preset_id": preset_id} if preset_id else {}),
|
|
97
106
|
**({"os_id": os_id} if os_id else {}),
|
|
@@ -574,7 +583,7 @@ class TimewebCloud(TimewebCloudBase):
|
|
|
574
583
|
from_project: int,
|
|
575
584
|
to_project: int,
|
|
576
585
|
resource_id: int,
|
|
577
|
-
resource_type:
|
|
586
|
+
resource_type: ResourceType,
|
|
578
587
|
):
|
|
579
588
|
"""Move resource to project."""
|
|
580
589
|
payload = {
|
|
@@ -983,6 +992,7 @@ class TimewebCloud(TimewebCloudBase):
|
|
|
983
992
|
proxy_protocol: bool = False,
|
|
984
993
|
force_https: bool = False,
|
|
985
994
|
backend_keepalive: bool = False,
|
|
995
|
+
network: Optional[dict] = None,
|
|
986
996
|
):
|
|
987
997
|
"""Create load balancer."""
|
|
988
998
|
payload = {
|
|
@@ -1000,6 +1010,7 @@ class TimewebCloud(TimewebCloudBase):
|
|
|
1000
1010
|
"is_use_proxy": proxy_protocol,
|
|
1001
1011
|
"is_ssl": force_https,
|
|
1002
1012
|
"is_keepalive": backend_keepalive,
|
|
1013
|
+
**({"network": network} if network else {}),
|
|
1003
1014
|
}
|
|
1004
1015
|
return self._request("POST", f"{self.api_url}/balancers", json=payload)
|
|
1005
1016
|
|
|
@@ -1063,7 +1074,7 @@ class TimewebCloud(TimewebCloudBase):
|
|
|
1063
1074
|
}
|
|
1064
1075
|
return self._request(
|
|
1065
1076
|
"DELETE",
|
|
1066
|
-
f"{self.api_url}/balancers{balancer_id}",
|
|
1077
|
+
f"{self.api_url}/balancers/{balancer_id}",
|
|
1067
1078
|
params=params,
|
|
1068
1079
|
)
|
|
1069
1080
|
|
|
@@ -1347,3 +1358,335 @@ class TimewebCloud(TimewebCloudBase):
|
|
|
1347
1358
|
def get_k8s_presets(self):
|
|
1348
1359
|
"""List available Kubernetes nodes presets."""
|
|
1349
1360
|
return self._request("GET", f"{self.api_url}/presets/k8s")
|
|
1361
|
+
|
|
1362
|
+
# -----------------------------------------------------------------------
|
|
1363
|
+
# Domains
|
|
1364
|
+
|
|
1365
|
+
def get_domains(self, limit: int = 100, offset: int = 0):
|
|
1366
|
+
"""Get domains list."""
|
|
1367
|
+
params = {"limit": limit, "offset": offset}
|
|
1368
|
+
return self._request("GET", f"{self.api_url}/domains", params=params)
|
|
1369
|
+
|
|
1370
|
+
def get_domain(self, fqdn: str):
|
|
1371
|
+
"""Get domain."""
|
|
1372
|
+
return self._request("GET", f"{self.api_url}/domains/{fqdn}")
|
|
1373
|
+
|
|
1374
|
+
def domain_turn_on_autoprolong(
|
|
1375
|
+
self,
|
|
1376
|
+
fqdn: str,
|
|
1377
|
+
is_autoprolong_enabled: bool = False,
|
|
1378
|
+
):
|
|
1379
|
+
"""Turn off/on autoprolong domain."""
|
|
1380
|
+
payload = {
|
|
1381
|
+
"is_autoprolong_enabled": is_autoprolong_enabled,
|
|
1382
|
+
}
|
|
1383
|
+
return self._request(
|
|
1384
|
+
"PATCH", f"{self.api_url}/domains/{fqdn}", json=payload
|
|
1385
|
+
)
|
|
1386
|
+
|
|
1387
|
+
def delete_domain(self, fqdn: str):
|
|
1388
|
+
"""Delete Domain."""
|
|
1389
|
+
return self._request("DELETE", f"{self.api_url}/domains/{fqdn}")
|
|
1390
|
+
|
|
1391
|
+
def add_domain(self, fqdn: str):
|
|
1392
|
+
"""Add Domain."""
|
|
1393
|
+
return self._request("POST", f"{self.api_url}/add-domain/{fqdn}")
|
|
1394
|
+
|
|
1395
|
+
def get_domain_dns_records(
|
|
1396
|
+
self, fqdn: str, limit: int = 100, offset: int = 0
|
|
1397
|
+
):
|
|
1398
|
+
"""Get domain DNS records."""
|
|
1399
|
+
params = {"limit": limit, "offset": offset}
|
|
1400
|
+
return self._request(
|
|
1401
|
+
"GET", f"{self.api_url}/domains/{fqdn}/dns-records", params=params
|
|
1402
|
+
)
|
|
1403
|
+
|
|
1404
|
+
def add_domain_dns_record(
|
|
1405
|
+
self,
|
|
1406
|
+
fqdn: str,
|
|
1407
|
+
dns_record_type: DNSRecordType,
|
|
1408
|
+
value: str,
|
|
1409
|
+
subdomain: Optional[str] = None,
|
|
1410
|
+
priority: Optional[int] = None,
|
|
1411
|
+
):
|
|
1412
|
+
"""Add DNS record to domain."""
|
|
1413
|
+
payload = {
|
|
1414
|
+
"type": dns_record_type,
|
|
1415
|
+
"value": value,
|
|
1416
|
+
**({"subdomain": subdomain} if subdomain else {}),
|
|
1417
|
+
**({"priority": priority} if priority else {}),
|
|
1418
|
+
}
|
|
1419
|
+
return self._request(
|
|
1420
|
+
"POST",
|
|
1421
|
+
f"{self.api_url}/domains/{fqdn}/dns-records",
|
|
1422
|
+
json=payload,
|
|
1423
|
+
)
|
|
1424
|
+
|
|
1425
|
+
def update_domain_dns_record(
|
|
1426
|
+
self,
|
|
1427
|
+
fqdn: str,
|
|
1428
|
+
record_id: int,
|
|
1429
|
+
dns_record_type: DNSRecordType,
|
|
1430
|
+
value: str,
|
|
1431
|
+
subdomain: Optional[str] = None,
|
|
1432
|
+
priority: Optional[int] = None,
|
|
1433
|
+
):
|
|
1434
|
+
"""Update DNS record on domain."""
|
|
1435
|
+
payload = {
|
|
1436
|
+
"type": dns_record_type,
|
|
1437
|
+
"value": value,
|
|
1438
|
+
**({"subdomain": subdomain} if subdomain else {}),
|
|
1439
|
+
**({"priority": priority} if priority else {}),
|
|
1440
|
+
}
|
|
1441
|
+
return self._request(
|
|
1442
|
+
"PATCH",
|
|
1443
|
+
f"{self.api_url}/domains/{fqdn}/dns-records/{record_id}",
|
|
1444
|
+
json=payload,
|
|
1445
|
+
)
|
|
1446
|
+
|
|
1447
|
+
def delete_domain_dns_record(self, fqdn: str, record_id: int):
|
|
1448
|
+
"""Delete DNS record on domain."""
|
|
1449
|
+
return self._request(
|
|
1450
|
+
"DELETE",
|
|
1451
|
+
f"{self.api_url}/domains/{fqdn}/dns-records/{record_id}",
|
|
1452
|
+
)
|
|
1453
|
+
|
|
1454
|
+
def add_subdomain(self, fqdn: str, subdomain_fqdn: str):
|
|
1455
|
+
"""Add subdomian tp domain."""
|
|
1456
|
+
return self._request(
|
|
1457
|
+
"POST",
|
|
1458
|
+
f"{self.api_url}/domains/{fqdn}/subdomains/{subdomain_fqdn}",
|
|
1459
|
+
)
|
|
1460
|
+
|
|
1461
|
+
def delete_subdomain(self, fqdn: str, subdomain_fqdn: str):
|
|
1462
|
+
"""Add subdomian tp domain."""
|
|
1463
|
+
return self._request(
|
|
1464
|
+
"DELETE",
|
|
1465
|
+
f"{self.api_url}/domains/{fqdn}/subdomains/{subdomain_fqdn}",
|
|
1466
|
+
)
|
|
1467
|
+
|
|
1468
|
+
# -----------------------------------------------------------------------
|
|
1469
|
+
# VPC
|
|
1470
|
+
|
|
1471
|
+
# API issue: Why some VPC methods belong to /api/v1 and others to /api/v2??
|
|
1472
|
+
|
|
1473
|
+
def get_vpcs(self):
|
|
1474
|
+
"""Return list of private networks."""
|
|
1475
|
+
return self._request("GET", f"{self.api_url_v2}/vpcs")
|
|
1476
|
+
|
|
1477
|
+
def get_vpc(self, vpc_id: str):
|
|
1478
|
+
"""Return network information."""
|
|
1479
|
+
return self._request("GET", f"{self.api_url_v2}/vpcs/{vpc_id}")
|
|
1480
|
+
|
|
1481
|
+
def create_vpc(
|
|
1482
|
+
self,
|
|
1483
|
+
name: str,
|
|
1484
|
+
subnet: IPv4Network,
|
|
1485
|
+
location: ServiceRegion,
|
|
1486
|
+
description: Optional[str] = None,
|
|
1487
|
+
):
|
|
1488
|
+
"""Create new virtual private network."""
|
|
1489
|
+
payload = {
|
|
1490
|
+
"name": name,
|
|
1491
|
+
"subnet_v4": subnet,
|
|
1492
|
+
"location": location,
|
|
1493
|
+
**({"description": description} if description else {}),
|
|
1494
|
+
}
|
|
1495
|
+
return self._request("POST", f"{self.api_url_v2}/vpcs", json=payload)
|
|
1496
|
+
|
|
1497
|
+
def update_vpc(
|
|
1498
|
+
self,
|
|
1499
|
+
vpc_id: str,
|
|
1500
|
+
name: Optional[str] = None,
|
|
1501
|
+
description: Optional[str] = None,
|
|
1502
|
+
):
|
|
1503
|
+
"""Update network information."""
|
|
1504
|
+
payload = {
|
|
1505
|
+
**({"name": name} if name else {}),
|
|
1506
|
+
**({"description": description} if description else {}),
|
|
1507
|
+
}
|
|
1508
|
+
return self._request(
|
|
1509
|
+
"PATCH",
|
|
1510
|
+
f"{self.api_url_v2}/vpcs/{vpc_id}",
|
|
1511
|
+
json=payload,
|
|
1512
|
+
)
|
|
1513
|
+
|
|
1514
|
+
def delete_vpc(self, vpc_id: str):
|
|
1515
|
+
"""Delete network."""
|
|
1516
|
+
return self._request("DELETE", f"{self.api_url_v1}/vpcs/{vpc_id}")
|
|
1517
|
+
|
|
1518
|
+
def get_services_in_vpc(self, vpc_id: str):
|
|
1519
|
+
"""Return network information."""
|
|
1520
|
+
return self._request(
|
|
1521
|
+
"GET", f"{self.api_url_v2}/vpcs/{vpc_id}/services"
|
|
1522
|
+
)
|
|
1523
|
+
|
|
1524
|
+
def get_vpc_ports(self, vpc_id: str):
|
|
1525
|
+
"""Return network information."""
|
|
1526
|
+
return self._request("GET", f"{self.api_url_v1}/vpcs/{vpc_id}/ports")
|
|
1527
|
+
|
|
1528
|
+
# -----------------------------------------------------------------------
|
|
1529
|
+
# Firewall
|
|
1530
|
+
|
|
1531
|
+
def get_firewall_groups(self, limit: int = 100, offset: int = 0):
|
|
1532
|
+
"""Get list of firewall groups."""
|
|
1533
|
+
params = {"limit": limit, "offset": offset}
|
|
1534
|
+
return self._request(
|
|
1535
|
+
"GET", f"{self.api_url}/firewall/groups", params=params
|
|
1536
|
+
)
|
|
1537
|
+
|
|
1538
|
+
def create_firewall_group(
|
|
1539
|
+
self, name: str, description: Optional[str] = None
|
|
1540
|
+
):
|
|
1541
|
+
payload = {
|
|
1542
|
+
"name": name,
|
|
1543
|
+
**({"description": description} if description else {}),
|
|
1544
|
+
}
|
|
1545
|
+
return self._request(
|
|
1546
|
+
"POST", f"{self.api_url}/firewall/groups", json=payload
|
|
1547
|
+
)
|
|
1548
|
+
|
|
1549
|
+
def get_firewall_group(self, group_id: UUID):
|
|
1550
|
+
return self._request(
|
|
1551
|
+
"GET", f"{self.api_url}/firewall/groups/{group_id}"
|
|
1552
|
+
)
|
|
1553
|
+
|
|
1554
|
+
def delete_firewall_group(self, group_id: UUID):
|
|
1555
|
+
return self._request(
|
|
1556
|
+
"DELETE", f"{self.api_url}/firewall/groups/{group_id}"
|
|
1557
|
+
)
|
|
1558
|
+
|
|
1559
|
+
def update_firewall_group(
|
|
1560
|
+
self,
|
|
1561
|
+
group_id: UUID,
|
|
1562
|
+
name: str,
|
|
1563
|
+
description: Optional[str] = None,
|
|
1564
|
+
):
|
|
1565
|
+
payload = {
|
|
1566
|
+
"name": name,
|
|
1567
|
+
**({"description": description} if description else {}),
|
|
1568
|
+
}
|
|
1569
|
+
return self._request(
|
|
1570
|
+
"PATCH", f"{self.api_url}/firewall/groups/{group_id}", json=payload
|
|
1571
|
+
)
|
|
1572
|
+
|
|
1573
|
+
def get_firewall_group_resources(
|
|
1574
|
+
self, group_id: UUID, limit: int = 100, offset: int = 0
|
|
1575
|
+
):
|
|
1576
|
+
params = {"limit": limit, "offset": offset}
|
|
1577
|
+
return self._request(
|
|
1578
|
+
"GET",
|
|
1579
|
+
f"{self.api_url}/firewall/groups/{group_id}/resources",
|
|
1580
|
+
params=params,
|
|
1581
|
+
)
|
|
1582
|
+
|
|
1583
|
+
def link_resource_to_firewall(
|
|
1584
|
+
self,
|
|
1585
|
+
group_id: UUID,
|
|
1586
|
+
resource_id: Union[str, int],
|
|
1587
|
+
resource_type: str,
|
|
1588
|
+
):
|
|
1589
|
+
if resource_type not in ["server", "dbaas", "balancer"]:
|
|
1590
|
+
raise ValueError("Invalid resource type")
|
|
1591
|
+
return self._request(
|
|
1592
|
+
"POST",
|
|
1593
|
+
f"{self.api_url}/firewall/groups/{group_id}/resources/{resource_id}",
|
|
1594
|
+
params={"resource_type": resource_type},
|
|
1595
|
+
)
|
|
1596
|
+
|
|
1597
|
+
def unlink_resource_from_firewall(
|
|
1598
|
+
self,
|
|
1599
|
+
group_id: UUID,
|
|
1600
|
+
resource_id: Union[str, int],
|
|
1601
|
+
resource_type: str,
|
|
1602
|
+
):
|
|
1603
|
+
if resource_type not in ["server", "dbaas", "balancer"]:
|
|
1604
|
+
raise ValueError("Invalid resource type")
|
|
1605
|
+
return self._request(
|
|
1606
|
+
"DELETE",
|
|
1607
|
+
f"{self.api_url}/firewall/groups/{group_id}/resources/{resource_id}",
|
|
1608
|
+
params={"resource_type": resource_type},
|
|
1609
|
+
)
|
|
1610
|
+
|
|
1611
|
+
def get_firewall_rules(
|
|
1612
|
+
self, group_id: UUID, limit: int = 100, offset: int = 0
|
|
1613
|
+
):
|
|
1614
|
+
"""Get list of firewall rules."""
|
|
1615
|
+
params = {"limit": limit, "offset": offset}
|
|
1616
|
+
return self._request(
|
|
1617
|
+
"GET",
|
|
1618
|
+
f"{self.api_url}/firewall/groups/{group_id}/rules",
|
|
1619
|
+
params=params,
|
|
1620
|
+
)
|
|
1621
|
+
|
|
1622
|
+
def create_firewall_rule(
|
|
1623
|
+
self,
|
|
1624
|
+
group_id: UUID,
|
|
1625
|
+
direction: FirewallDirection,
|
|
1626
|
+
proto: FirewallProto,
|
|
1627
|
+
cidr: Union[IPv4Network, IPv6Network],
|
|
1628
|
+
port: Optional[str] = None,
|
|
1629
|
+
description: Optional[str] = None,
|
|
1630
|
+
):
|
|
1631
|
+
payload = {
|
|
1632
|
+
**({"description": description} if description else {}),
|
|
1633
|
+
**({} if proto == FirewallProto.ICMP.value else {"port": port}),
|
|
1634
|
+
"direction": direction,
|
|
1635
|
+
"protocol": proto,
|
|
1636
|
+
"cidr": cidr,
|
|
1637
|
+
}
|
|
1638
|
+
return self._request(
|
|
1639
|
+
"POST",
|
|
1640
|
+
f"{self.api_url}/firewall/groups/{group_id}/rules",
|
|
1641
|
+
json=payload,
|
|
1642
|
+
)
|
|
1643
|
+
|
|
1644
|
+
def get_firewall_rule(self, group_id: UUID, rule_id: UUID):
|
|
1645
|
+
return self._request(
|
|
1646
|
+
"GET", f"{self.api_url}/firewall/groups/{group_id}/rules/{rule_id}"
|
|
1647
|
+
)
|
|
1648
|
+
|
|
1649
|
+
def delete_firewall_rule(self, group_id: UUID, rule_id: UUID):
|
|
1650
|
+
return self._request(
|
|
1651
|
+
"DELETE",
|
|
1652
|
+
f"{self.api_url}/firewall/groups/{group_id}/rules/{rule_id}",
|
|
1653
|
+
)
|
|
1654
|
+
|
|
1655
|
+
def update_firewall_rule(
|
|
1656
|
+
self,
|
|
1657
|
+
group_id: UUID,
|
|
1658
|
+
rule_id: UUID,
|
|
1659
|
+
direction: FirewallDirection,
|
|
1660
|
+
proto: FirewallProto,
|
|
1661
|
+
cidr: Union[IPv4Network, IPv6Network],
|
|
1662
|
+
port: Optional[str] = None,
|
|
1663
|
+
description: Optional[str] = None,
|
|
1664
|
+
):
|
|
1665
|
+
payload = {
|
|
1666
|
+
**({"description": description} if description else {}),
|
|
1667
|
+
**({} if proto == FirewallProto.ICMP.value else {"port": port}),
|
|
1668
|
+
"direction": direction,
|
|
1669
|
+
"protocol": proto,
|
|
1670
|
+
"cidr": cidr,
|
|
1671
|
+
}
|
|
1672
|
+
return self._request(
|
|
1673
|
+
"PATCH",
|
|
1674
|
+
f"{self.api_url}/firewall/groups/{group_id}/rules/{rule_id}",
|
|
1675
|
+
json=payload,
|
|
1676
|
+
)
|
|
1677
|
+
|
|
1678
|
+
def get_resource_firewall_groups(
|
|
1679
|
+
self,
|
|
1680
|
+
resource_id: Union[int, str],
|
|
1681
|
+
resource_type: str,
|
|
1682
|
+
limit: int = 100,
|
|
1683
|
+
offset: int = 0,
|
|
1684
|
+
):
|
|
1685
|
+
params = {"limit": limit, "offset": offset}
|
|
1686
|
+
if resource_type not in ["server", "dbaas", "balancer"]:
|
|
1687
|
+
raise ValueError("Invalid resource type")
|
|
1688
|
+
return self._request(
|
|
1689
|
+
"GET",
|
|
1690
|
+
f"{self.api_url}/firewall/service/{resource_type}/{resource_id}",
|
|
1691
|
+
params=params,
|
|
1692
|
+
)
|
|
@@ -111,8 +111,8 @@ class BackupInterval(str, Enum):
|
|
|
111
111
|
MONTH = "month"
|
|
112
112
|
|
|
113
113
|
|
|
114
|
-
class
|
|
115
|
-
"""
|
|
114
|
+
class ResourceType(str, Enum):
|
|
115
|
+
"""Resource types."""
|
|
116
116
|
|
|
117
117
|
SERVER = "server"
|
|
118
118
|
BALANCER = "balancer"
|
|
@@ -162,3 +162,29 @@ class LoadBalancerAlgo(str, Enum):
|
|
|
162
162
|
|
|
163
163
|
ROUND_ROBIN = "roundrobin"
|
|
164
164
|
LEAST_CONNECTIONS = "leastconn"
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
class DNSRecordType(str, Enum):
|
|
168
|
+
"""Type DNS record"""
|
|
169
|
+
|
|
170
|
+
TXT = "TXT"
|
|
171
|
+
SRV = "SRV"
|
|
172
|
+
CNAME = "CNAME"
|
|
173
|
+
AAAA = "AAAA"
|
|
174
|
+
MX = "MX"
|
|
175
|
+
A = "A"
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
class FirewallProto(str, Enum):
|
|
179
|
+
"""Protocols supported by Firewall service."""
|
|
180
|
+
|
|
181
|
+
TCP = "tcp"
|
|
182
|
+
UDP = "udp"
|
|
183
|
+
ICMP = "icmp"
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
class FirewallDirection(str, Enum):
|
|
187
|
+
"""Traffic directions for Firewall service."""
|
|
188
|
+
|
|
189
|
+
INGRESS = "ingress"
|
|
190
|
+
EGRESS = "egress"
|