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.

Files changed (33) hide show
  1. {twc_cli-2.3.0 → twc_cli-2.4.1}/CHANGELOG.md +24 -0
  2. {twc_cli-2.3.0 → twc_cli-2.4.1}/PKG-INFO +1 -1
  3. {twc_cli-2.3.0 → twc_cli-2.4.1}/pyproject.toml +1 -1
  4. {twc_cli-2.3.0 → twc_cli-2.4.1}/twc/__main__.py +11 -1
  5. {twc_cli-2.3.0 → twc_cli-2.4.1}/twc/__version__.py +1 -1
  6. {twc_cli-2.3.0 → twc_cli-2.4.1}/twc/api/base.py +3 -2
  7. {twc_cli-2.3.0 → twc_cli-2.4.1}/twc/api/client.py +349 -6
  8. {twc_cli-2.3.0 → twc_cli-2.4.1}/twc/api/types.py +28 -2
  9. {twc_cli-2.3.0 → twc_cli-2.4.1}/twc/commands/__init__.py +3 -0
  10. {twc_cli-2.3.0 → twc_cli-2.4.1}/twc/commands/account.py +30 -40
  11. {twc_cli-2.3.0 → twc_cli-2.4.1}/twc/commands/balancer.py +38 -36
  12. {twc_cli-2.3.0 → twc_cli-2.4.1}/twc/commands/common.py +8 -4
  13. {twc_cli-2.3.0 → twc_cli-2.4.1}/twc/commands/config.py +28 -1
  14. twc_cli-2.4.1/twc/commands/domain.py +519 -0
  15. twc_cli-2.4.1/twc/commands/firewall.py +654 -0
  16. {twc_cli-2.3.0 → twc_cli-2.4.1}/twc/commands/image.py +14 -25
  17. {twc_cli-2.3.0 → twc_cli-2.4.1}/twc/commands/project.py +2 -2
  18. {twc_cli-2.3.0 → twc_cli-2.4.1}/twc/commands/server.py +16 -5
  19. twc_cli-2.4.1/twc/commands/vpc.py +293 -0
  20. {twc_cli-2.3.0 → twc_cli-2.4.1}/twc/fmt.py +15 -0
  21. {twc_cli-2.3.0 → twc_cli-2.4.1}/twc/typerx.py +7 -2
  22. {twc_cli-2.3.0 → twc_cli-2.4.1}/twc/vars.py +3 -2
  23. {twc_cli-2.3.0 → twc_cli-2.4.1}/COPYING +0 -0
  24. {twc_cli-2.3.0 → twc_cli-2.4.1}/README.md +0 -0
  25. {twc_cli-2.3.0 → twc_cli-2.4.1}/twc/__init__.py +0 -0
  26. {twc_cli-2.3.0 → twc_cli-2.4.1}/twc/api/__init__.py +0 -0
  27. {twc_cli-2.3.0 → twc_cli-2.4.1}/twc/api/exceptions.py +0 -0
  28. {twc_cli-2.3.0 → twc_cli-2.4.1}/twc/apiwrap.py +0 -0
  29. {twc_cli-2.3.0 → twc_cli-2.4.1}/twc/commands/database.py +0 -0
  30. {twc_cli-2.3.0 → twc_cli-2.4.1}/twc/commands/kubernetes.py +0 -0
  31. {twc_cli-2.3.0 → twc_cli-2.4.1}/twc/commands/ssh_key.py +0 -0
  32. {twc_cli-2.3.0 → twc_cli-2.4.1}/twc/commands/storage.py +0 -0
  33. {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
  ## Добавлено
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: twc-cli
3
- Version: 2.3.0
3
+ Version: 2.4.1
4
4
  Summary: Timeweb Cloud Command Line Interface.
5
5
  Home-page: https://github.com/timeweb-cloud/twc
6
6
  License: MIT
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "twc-cli"
3
- version = "2.3.0"
3
+ version = "2.4.1"
4
4
  description = "Timeweb Cloud Command Line Interface."
5
5
  authors = ["ge <dev@timeweb.cloud>"]
6
6
  homepage = "https://github.com/timeweb-cloud/twc"
@@ -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(help=__doc__)
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")
@@ -12,5 +12,5 @@
12
12
  import sys
13
13
 
14
14
 
15
- __version__ = "2.3.0"
15
+ __version__ = "2.4.1"
16
16
  __pyversion__ = sys.version.replace("\n", "")
@@ -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] = API_PATH,
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
- ProjectResource,
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 = False,
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
- "is_local_network": is_local_network,
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: ProjectResource,
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 ProjectResource(str, Enum):
115
- """Project resource types."""
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"
@@ -10,3 +10,6 @@ from .database import database
10
10
  from .storage import storage
11
11
  from .balancer import balancer
12
12
  from .kubernetes import cluster
13
+ from .domain import domain
14
+ from .vpc import vpc
15
+ from .firewall import firewall