diff --git a/README.md b/README.md index 5e8fbf0..f6ba2d2 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ print(account_status) - [x] Аккаунт - [x] Базы данных - - [ ] Балансировщики + - [x] Балансировщики - [x] Выделенные серверы - [ ] Домены - [ ] Облачные серверы diff --git a/pyproject.toml b/pyproject.toml index b3f5746..fd8aef3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "timeweb-cloud" -version = "0.4.1" +version = "0.5.0" description = "Timeweb Cloud API wrapper" authors = ["Maxim Mosin "] license = "MIT" @@ -38,7 +38,7 @@ requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" [tool.bumpver] -current_version = "0.4.1" +current_version = "0.5.0" version_pattern = "MAJOR.MINOR.PATCH" commit_message = "Bump version {old_version} -> {new_version}" commit = false diff --git a/src/timeweb/__meta.py b/src/timeweb/__meta.py index ade362a..52a46e6 100644 --- a/src/timeweb/__meta.py +++ b/src/timeweb/__meta.py @@ -1,4 +1,4 @@ # -*- coding: utf-8 -*- '''Timeweb Cloud package metadata''' -__version__ = '0.4.1' +__version__ = '0.5.0' __author__ = 'Maxim Mosin ' diff --git a/src/timeweb/async_api/api.py b/src/timeweb/async_api/api.py index 27668e8..8d2e533 100644 --- a/src/timeweb/async_api/api.py +++ b/src/timeweb/async_api/api.py @@ -11,6 +11,7 @@ from .dedics import DedicsAPI from .account import AccountAPI from .ssh_keys import SSHKeysAPI +from .balancers import BalancersAPI class Servers: @@ -42,6 +43,7 @@ class AsyncTimeweb: s3 (BucketsAPI): API для работы с S3-хранилищами. dbs (DatabasesAPI): API для работы с базами данных. servers (Servers): API для работы с серверами. + balancers (BalancersAPI): API для работы с балансировщиками. ''' def __init__(self, token: str, client: AsyncClient | None = None): @@ -59,3 +61,4 @@ def __init__(self, token: str, client: AsyncClient | None = None): self.s3 = BucketsAPI(token, client) self.dbs = DatabasesAPI(token, client) self.servers = Servers(token, client) + self.balancers = BalancersAPI(token, client) diff --git a/src/timeweb/async_api/balancers.py b/src/timeweb/async_api/balancers.py new file mode 100644 index 0000000..1341b1c --- /dev/null +++ b/src/timeweb/async_api/balancers.py @@ -0,0 +1,344 @@ +# -*- coding: utf-8 -*- +'''Методы API для работы с балансировщиками. + +Балансировщик позволяет распределять входящий трафик между несколькими серверами для повышения доступности и отказоустойчивости вашего сервиса. + +Документация: https://timeweb.cloud/api-docs#tag/Balansirovshiki''' +import logging +from ipaddress import IPv4Address, IPv6Address + +from httpx import AsyncClient + +from .base import BaseAsyncClient +from ..schemas import balancers as schemas + + +class BalancersAPI(BaseAsyncClient): + '''Клиент для работы с API балансировщиками Timeweb Cloud''' + + def __init__(self, token: str, client: AsyncClient | None = None): + '''Инициализация клиента. + Args: + token (str): API токен. + client (AsyncClient | None, optional): HTTPX клиент. Defaults to None. + ''' + super().__init__(token, client) + self.log = logging.getLogger('timeweb') + + async def get_balancers(self) -> schemas.BalancersResponse: + '''Получить список балансировщиков. + + Returns: + BalancersResponse: Список балансировщиков. + ''' + balancers = await self._request( + 'GET', '/balancers' + ) + return schemas.BalancersResponse(**balancers.json()) + + async def create( + self, name: str, algo: schemas.BalancerAlgorithm | str, is_sticky: bool, + is_use_proxy: bool, is_ssl: bool, is_keepalive: bool, port: int, + proto: schemas.Protocol | str, path: str, inter: int, timeout: int, + fall: int, rise: int, preset_id: int + ) -> schemas.BalancerResponse: + '''Создать балансировщик. + + Args: + name (str): Название балансировщика. + algo (BalancerAlgorithm | str): Алгоритм переключений балансировщика. + is_sticky (bool): Сохраняется ли сессия. + is_use_proxy (bool): Выступает ли балансировщик в качестве прокси. + is_ssl (bool): Требуется ли перенаправление на SSL. + is_keepalive (bool): Выдает ли балансировщик сигнал о проверке жизнеспособности. + port (int): Порт балансировщика. + proto (Protocol | str): Протокол балансировщика. + path (str): Адрес балансировщика. + inter (int): Интервал проверки. + timeout (int): Таймаут ответа балансировщика. + fall (int): Порог количества ошибок. + rise (int): Порог количества успешных проверок. + preset_id (int): UID тарифа балансировщика. + + Returns: + BalancerResponse: Балансировщик. + ''' + data = { + 'name': name, + 'is_sticky': is_sticky, + 'is_use_proxy': is_use_proxy, + 'is_ssl': is_ssl, + 'is_keepalive': is_keepalive, + 'port': port, + 'path': path, + 'inter': inter, + 'timeout': timeout, + 'fall': fall, + 'rise': rise, + 'preset_id': preset_id + } + if isinstance(algo, schemas.BalancerAlgorithm): + data['algo'] = algo.value + else: + data['algo'] = algo + if isinstance(proto, schemas.Protocol): + data['proto'] = proto.value + else: + data['proto'] = proto + balancer = await self._request( + 'POST', '/balancers', json=data + ) + return schemas.BalancerResponse(**balancer.json()) + + async def get(self, balancer_id: int) -> schemas.BalancerResponse: + '''Получить балансировщик. + + Args: + balancer_id (int): UID балансировщика. + + Returns: + BalancerResponse: Балансировщик. + ''' + balancer = await self._request( + 'GET', f'/balancers/{balancer_id}' + ) + return schemas.BalancerResponse(**balancer.json()) + + async def update( + self, balancer_id: int, name: str | None = None, + algo: schemas.BalancerAlgorithm | str | None = None, + is_sticky: bool | None = None, is_use_proxy: bool | None = None, + is_ssl: bool | None = None, is_keepalive: bool | None = None, + port: int | None = None, proto: schemas.Protocol | str | None = None, + path: str | None = None, inter: int | None = None, + timeout: int | None = None, fall: int | None = None, + rise: int | None = None, preset_id: int | None = None + ) -> schemas.BalancerResponse: + '''Обновить балансировщик. + + Args: + balancer_id (int): UID балансировщика. + name (str | None, optional): Название балансировщика. Defaults to None. + algo (BalancerAlgorithm | str | None, optional): Алгоритм переключений балансировщика. Defaults to None. + is_sticky (bool | None, optional): Сохраняется ли сессия. Defaults to None. + is_use_proxy (bool | None, optional): Выступает ли балансировщик в качестве прокси. Defaults to None. + is_ssl (bool | None, optional): Требуется ли перенаправление на SSL. Defaults to None. + is_keepalive (bool | None, optional): Выдает ли балансировщик сигнал о проверке жизнеспособности. Defaults to None. + port (int | None, optional): Порт балансировщика. Defaults to None. + proto (Protocol | str | None, optional): Протокол балансировщика. Defaults to None. + path (str | None, optional): Адрес балансировщика. Defaults to None. + inter (int | None, optional): Интервал проверки. Defaults to None. + timeout (int | None, optional): Таймаут ответа балансировщика. Defaults to None. + fall (int | None, optional): Порог количества ошибок. Defaults to None. + rise (int | None, optional): Порог количества успешных проверок. Defaults to None. + preset_id (int | None, optional): UID тарифа балансировщика. Defaults to None. + + Returns: + BalancerResponse: Балансировщик. + ''' + data: dict[str, str | int] = {} + if name is not None: + data['name'] = name + if algo is not None: + if isinstance(algo, schemas.BalancerAlgorithm): + data['algo'] = algo.value + else: + data['algo'] = algo + if is_sticky: + data['is_sticky'] = is_sticky + if is_use_proxy: + data['is_use_proxy'] = is_use_proxy + if is_ssl: + data['is_ssl'] = is_ssl + if is_keepalive: + data['is_keepalive'] = is_keepalive + if port is not None: + data['port'] = port + if proto is not None: + if isinstance(proto, schemas.Protocol): + data['proto'] = proto.value + else: + data['proto'] = proto + if path is not None: + data['path'] = path + if inter is not None: + data['inter'] = inter + if timeout is not None: + data['timeout'] = timeout + if fall is not None: + data['fall'] = fall + if rise is not None: + data['rise'] = rise + if preset_id is not None: + data['preset_id'] = preset_id + balancer = await self._request( + 'PATCH', f'/balancers/{balancer_id}', json=data + ) + return schemas.BalancerResponse(**balancer.json()) + + async def delete(self, balancer_id: int) -> bool: + '''Удалить балансировщик. + + Args: + balancer_id (int): UID балансировщика. + + Returns: + bool: Успешность удаления. + ''' + await self._request( + 'DELETE', f'/balancers/{balancer_id}' + ) + return True + + async def get_balancer_ips(self, balancer_id: int) -> schemas.BalancerIPsResponse: + '''Получить IP балансировщика. + + Args: + balancer_id (int): UID IP балансировщика. + + Returns: + BalancerIPsResponse: IP адреса балансировщика. + ''' + balancer_ips = await self._request( + 'GET', f'/balancers/{balancer_id}/ips' + ) + return schemas.BalancerIPsResponse(**balancer_ips.json()) + + async def add_balancer_ips( + self, balancer_id: int, ips: list[str | IPv4Address | IPv6Address] + ) -> bool: + '''Добавить IP адреса балансировщика. + + Args: + balancer_id (int): UID балансировщика. + ips (list[str | IPv4Address | IPv6Address]): IP адреса. + + Returns: + bool: IP адреса добавлены. + ''' + resp = await self._request( + 'POST', f'/balancers/{balancer_id}/ips', json={'ips': ips} + ) + return resp.is_success + + async def delete_balancer_ips( + self, balancer_id: int, ips: list[str | IPv4Address | IPv6Address] + ) -> bool: + '''Удаление IP адресов балансировщика. + + Args: + balancer_id (int): UID балансировщика. + ips (list[str | IPv4Address | IPv6Address]): IP адреса. + + Returns: + bool: IP адреса удалены. + ''' + resp = await self._request( + 'DELETE', f'/balancers/{balancer_id}/ips', json={'ips': ips} + ) + return resp.is_success + + async def get_balancer_rules(self, balancer_id: int) -> schemas.BalancerRulesResponse: + '''Получить правила балансировщика. + + Args: + balancer_id (int): UID балансировщика. + + Returns: + BalancerRulesResponse: Правила балансировщика. + ''' + balancer_rules = await self._request( + 'GET', f'/balancers/{balancer_id}/rules' + ) + return schemas.BalancerRulesResponse(**balancer_rules.json()) + + async def add_balancer_rule( + self, balancer_id: int, balancer_proto: schemas.Protocol | str, + balancer_port: int, server_proto: schemas.Protocol | str, + server_port: int + ) -> schemas.BalancerRuleResponse: + '''Добавить правило балансировщика. + + Args: + balancer_id (int): UID балансировщика. + balancer_proto (Protocol | str): Протокол балансировщика. + balancer_port (int): Порт балансировщика. + server_proto (Protocol | str): Протокол сервера. + server_port (int): Порт сервера. + + Returns: + BalancerRuleResponse: Добавленное правило. + ''' + data: dict[str, str | int] = {} + if isinstance(balancer_proto, schemas.Protocol): + data['balancer_proto'] = balancer_proto.value + else: + data['balancer_proto'] = balancer_proto + data['balancer_port'] = balancer_port + if isinstance(server_proto, schemas.Protocol): + data['server_proto'] = server_proto.value + else: + data['server_proto'] = server_proto + data['server_port'] = server_port + balancer_rule = await self._request( + 'POST', f'/balancers/{balancer_id}/rules', json=data + ) + return schemas.BalancerRuleResponse(**balancer_rule.json()) + + async def update_balancer_rule( + self, balancer_id: int, rule_id: int, + balancer_proto: schemas.Protocol | str, balancer_port: int, + server_proto: schemas.Protocol | str, server_port: int + ) -> schemas.BalancerRuleResponse: + '''Добавить правило балансировщика. + + Args: + balancer_id (int): UID балансировщика. + rule_id (int): UID правила. + balancer_proto (Protocol | str): Протокол балансировщика. + balancer_port (int): Порт балансировщика. + server_proto (Protocol | str): Протокол сервера. + server_port (int): Порт сервера. + + Returns: + BalancerRuleResponse: Обнавлённое правило. + ''' + data: dict[str, str | int] = {} + if isinstance(balancer_proto, schemas.Protocol): + data['balancer_proto'] = balancer_proto.value + else: + data['balancer_proto'] = balancer_proto + data['balancer_port'] = balancer_port + if isinstance(server_proto, schemas.Protocol): + data['server_proto'] = server_proto.value + else: + data['server_proto'] = server_proto + data['server_port'] = server_port + balancer_rule = await self._request( + 'PATCH', f'/balancers/{balancer_id}/rules/{rule_id}', json=data + ) + return schemas.BalancerRuleResponse(**balancer_rule.json()) + + async def delete_balancer_rule(self, balancer_id: int, rule_id: int) -> bool: + '''Удалить правило балансировщика. + + Args: + balancer_id (int): UID балансировщика. + rule_id (int): UID правила. + + Returns: + bool: Правило удалено. + ''' + resp = await self._request( + 'DELETE', f'/balancers/{balancer_id}/rules/{rule_id}' + ) + return resp.is_success + + async def get_presets(self) -> schemas.BalancerPresetsResponse: + '''Получить список тарифов балансировщиков. + + Returns: + BalancerPresetsResponse: Список тарифов. + ''' + balancer_presets = await self._request('GET', '/presets/balancers') + return schemas.BalancerPresetsResponse(**balancer_presets.json()) diff --git a/src/timeweb/schemas/balancers/__init__.py b/src/timeweb/schemas/balancers/__init__.py new file mode 100644 index 0000000..722c717 --- /dev/null +++ b/src/timeweb/schemas/balancers/__init__.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +# flake8: noqa +'''Модели для работы с балансировщиками''' +from .presets import ( + BalancerPreset, BalancerPresetsResponse +) +from .balancers import ( + Balancer, BalancerRule, BalancerStatus, + BalancerAlgorithm, Protocol, BalancerResponse, + BalancersResponse, BalancerRuleResponse, + BalancerRulesResponse, BalancerIPsResponse +) diff --git a/src/timeweb/schemas/balancers/balancers.py b/src/timeweb/schemas/balancers/balancers.py new file mode 100644 index 0000000..d7378ad --- /dev/null +++ b/src/timeweb/schemas/balancers/balancers.py @@ -0,0 +1,105 @@ +# -*- coding: utf-8 -*- +'''Модели для работы с балансировщиками''' +from enum import Enum +from datetime import datetime +from ipaddress import IPv4Address, IPv6Address + +from pydantic import BaseModel, Field + +from ..base import ResponseWithMeta, BaseResponse + + +class Protocol(str, Enum): + '''Протокол балансировщика''' + HTTP2 = 'http2' + HTTP = 'http' + HTTPS = 'https' + TCP = 'tcp' + + +class BalancerAlgorithm(str, Enum): + '''Алгоритм балансировки''' + ROUND_ROBIN = 'roundrobin' + LEAST_CONNECTIONS = 'leastconn' + + +class BalancerStatus(str, Enum): + '''Статус балансировщика''' + STARTED = 'started' + STOPED = 'stoped' + STARTING = 'starting' + NO_PAID = 'no_paid' + + +class BalancerRule(BaseModel): + '''Правило балансировщика''' + id: int = Field(..., description='UID правила') + balancer_proto: Protocol = Field(..., description='Протокол балансировщика') + balancer_port: int = Field(..., description='Порт балансировщика') + server_proto: Protocol = Field(..., description='Протокол сервера') + server_port: int = Field(..., description='Порт сервера') + + +class Balancer(BaseModel): + '''Балансировщик''' + id: int = Field(..., description='UID балансировщика') + algo: BalancerAlgorithm = Field( + ..., description='Алгоритм переключений балансировщика.' + ) + created_at: datetime = Field( + ..., description='Дата создания балансировщика.' + ) + fall: int = Field(..., description='Порог количества ошибок.') + inter: int = Field(..., description='Интервал проверки.') + ip: IPv4Address | None = Field( + None, description='IP адрес балансировщика.' + ) + local_ip: IPv4Address | None = Field( + None, description='Локальный IP адрес балансировщика.' + ) + is_keepalive: bool = Field( + ..., description='Выдает ли балансировщик сигнал о проверке жизнеспособности.' + ) + name: str = Field(..., description='Название балансировщика.') + path: str = Field(..., description='Адрес балансировщика.') + proto: Protocol = Field(..., description='Протокол балансировщика.') + rise: int = Field(..., description='Порог количества успешных проверок.') + preset_id: int = Field(..., description='UID тарифа балансировщика.') + is_ssl: bool = Field(..., description='Требуется ли перенаправление на SSL.') + status: BalancerStatus = Field(..., description='Статус балансировщика.') + is_sticky: bool = Field(..., description='Сохраняется ли сессия.') + timeout: int = Field(..., description='Таймаут ответа балансировщика.') + is_use_proxy: bool = Field( + ..., description='Выступает ли балансировщик в качестве прокси.' + ) + ips: list[str | IPv4Address | IPv6Address] = Field( # В докуменатции точно не указан тип, опираемся на название. + ..., description='Список IP адресов, привязанных к балансировщику.' + ) + rules: list[BalancerRule] = Field( + ..., description='Список правил балансировщика.' + ) + + +class BalancerResponse(BaseResponse): + '''Ответ с балансировщиком''' + balancer: Balancer = Field(..., description='Балансировщик.') + + +class BalancersResponse(ResponseWithMeta): + '''Ответ со списком балансировщиков''' + balancers: list[Balancer] = Field(..., description='Список балансировщиков.') + + +class BalancerRuleResponse(BaseResponse): + '''Ответ с правилом балансировщика''' + rule: BalancerRule = Field(..., description='Правило балансировщика.') + + +class BalancerRulesResponse(ResponseWithMeta): + '''Ответ со списком правил балансировщика''' + rules: list[BalancerRule] = Field(..., description='Список правил балансировщика.') + + +class BalancerIPsResponse(ResponseWithMeta): + '''Ответ со списком IP адресов балансировщика''' + ips: list[str | IPv4Address | IPv6Address] = Field(..., description='Список IP адресов балансировщика.') diff --git a/src/timeweb/schemas/balancers/presets.py b/src/timeweb/schemas/balancers/presets.py new file mode 100644 index 0000000..109dd0d --- /dev/null +++ b/src/timeweb/schemas/balancers/presets.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +'''Модели для работы с тарифами балансировщиков''' +from pydantic import BaseModel, Field + +from ..base import ResponseWithMeta + + +class BalancerPreset(BaseModel): + '''Тариф балансировщика''' + id: int = Field(..., description='UID тарифа') + description: str = Field(..., description='Описание тарифа') + description_short: str = Field(..., description='Краткое описание тарифа') + bandwidth: int = Field(..., description='Пропускная способность') + replica_count: int = Field(..., description='Количество реплик') + request_per_second: str = Field(..., description='Количество запросов в секунду') + price: int = Field(..., description='Цена тарифа') + location: str = Field(..., description='Расположение тарифа') + + +class BalancerPresetsResponse(ResponseWithMeta): + '''Список тарифов балансировщиков''' + balancers_presets: list[BalancerPreset] = Field(..., description='Список тарифов балансировщиков') diff --git a/src/timeweb/sync_api/api.py b/src/timeweb/sync_api/api.py index 54b6ca0..21c3d1f 100644 --- a/src/timeweb/sync_api/api.py +++ b/src/timeweb/sync_api/api.py @@ -11,6 +11,7 @@ from .dedics import DedicsAPI from .account import AccountAPI from .ssh_keys import SSHKeysAPI +from .balancers import BalancersAPI class Servers: @@ -41,6 +42,7 @@ class Timeweb: s3 (BucketsAPI): API для работы с S3-хранилищами. dbs (DatabasesAPI): API для работы с базами данных. servers (Servers): API для работы с серверами. + balancers (BalancersAPI): API для работы с балансировщиками. ''' def __init__(self, token: str, client: Client | None = None): @@ -58,3 +60,4 @@ def __init__(self, token: str, client: Client | None = None): self.s3 = BucketsAPI(token, client) self.dbs = DatabasesAPI(token, client) self.servers = Servers(token, client) + self.balancers = BalancersAPI(token, client) diff --git a/src/timeweb/sync_api/balancers.py b/src/timeweb/sync_api/balancers.py new file mode 100644 index 0000000..b08cfb5 --- /dev/null +++ b/src/timeweb/sync_api/balancers.py @@ -0,0 +1,344 @@ +# -*- coding: utf-8 -*- +'''Методы API для работы с балансировщиками. + +Балансировщик позволяет распределять входящий трафик между несколькими серверами для повышения доступности и отказоустойчивости вашего сервиса. + +Документация: https://timeweb.cloud/api-docs#tag/Balansirovshiki''' +import logging +from ipaddress import IPv4Address, IPv6Address + +from httpx import Client + +from .base import BaseClient +from ..schemas import balancers as schemas + + +class BalancersAPI(BaseClient): + '''Клиент для работы с API балансировщиками Timeweb Cloud''' + + def __init__(self, token: str, client: Client | None = None): + '''Инициализация клиента. + Args: + token (str): API токен. + client (Client | None, optional): HTTPX клиент. Defaults to None. + ''' + super().__init__(token, client) + self.log = logging.getLogger('timeweb') + + def get_balancers(self) -> schemas.BalancersResponse: + '''Получить список балансировщиков. + + Returns: + BalancersResponse: Список балансировщиков. + ''' + balancers = self._request( + 'GET', '/balancers' + ) + return schemas.BalancersResponse(**balancers.json()) + + def create( + self, name: str, algo: schemas.BalancerAlgorithm | str, is_sticky: bool, + is_use_proxy: bool, is_ssl: bool, is_keepalive: bool, port: int, + proto: schemas.Protocol | str, path: str, inter: int, timeout: int, + fall: int, rise: int, preset_id: int + ) -> schemas.BalancerResponse: + '''Создать балансировщик. + + Args: + name (str): Название балансировщика. + algo (BalancerAlgorithm | str): Алгоритм переключений балансировщика. + is_sticky (bool): Сохраняется ли сессия. + is_use_proxy (bool): Выступает ли балансировщик в качестве прокси. + is_ssl (bool): Требуется ли перенаправление на SSL. + is_keepalive (bool): Выдает ли балансировщик сигнал о проверке жизнеспособности. + port (int): Порт балансировщика. + proto (Protocol | str): Протокол балансировщика. + path (str): Адрес балансировщика. + inter (int): Интервал проверки. + timeout (int): Таймаут ответа балансировщика. + fall (int): Порог количества ошибок. + rise (int): Порог количества успешных проверок. + preset_id (int): UID тарифа балансировщика. + + Returns: + BalancerResponse: Балансировщик. + ''' + data = { + 'name': name, + 'is_sticky': is_sticky, + 'is_use_proxy': is_use_proxy, + 'is_ssl': is_ssl, + 'is_keepalive': is_keepalive, + 'port': port, + 'path': path, + 'inter': inter, + 'timeout': timeout, + 'fall': fall, + 'rise': rise, + 'preset_id': preset_id + } + if isinstance(algo, schemas.BalancerAlgorithm): + data['algo'] = algo.value + else: + data['algo'] = algo + if isinstance(proto, schemas.Protocol): + data['proto'] = proto.value + else: + data['proto'] = proto + balancer = self._request( + 'POST', '/balancers', json=data + ) + return schemas.BalancerResponse(**balancer.json()) + + def get(self, balancer_id: int) -> schemas.BalancerResponse: + '''Получить балансировщик. + + Args: + balancer_id (int): UID балансировщика. + + Returns: + BalancerResponse: Балансировщик. + ''' + balancer = self._request( + 'GET', f'/balancers/{balancer_id}' + ) + return schemas.BalancerResponse(**balancer.json()) + + def update( + self, balancer_id: int, name: str | None = None, + algo: schemas.BalancerAlgorithm | str | None = None, + is_sticky: bool | None = None, is_use_proxy: bool | None = None, + is_ssl: bool | None = None, is_keepalive: bool | None = None, + port: int | None = None, proto: schemas.Protocol | str | None = None, + path: str | None = None, inter: int | None = None, + timeout: int | None = None, fall: int | None = None, + rise: int | None = None, preset_id: int | None = None + ) -> schemas.BalancerResponse: + '''Обновить балансировщик. + + Args: + balancer_id (int): UID балансировщика. + name (str | None, optional): Название балансировщика. Defaults to None. + algo (BalancerAlgorithm | str | None, optional): Алгоритм переключений балансировщика. Defaults to None. + is_sticky (bool | None, optional): Сохраняется ли сессия. Defaults to None. + is_use_proxy (bool | None, optional): Выступает ли балансировщик в качестве прокси. Defaults to None. + is_ssl (bool | None, optional): Требуется ли перенаправление на SSL. Defaults to None. + is_keepalive (bool | None, optional): Выдает ли балансировщик сигнал о проверке жизнеспособности. Defaults to None. + port (int | None, optional): Порт балансировщика. Defaults to None. + proto (Protocol | str | None, optional): Протокол балансировщика. Defaults to None. + path (str | None, optional): Адрес балансировщика. Defaults to None. + inter (int | None, optional): Интервал проверки. Defaults to None. + timeout (int | None, optional): Таймаут ответа балансировщика. Defaults to None. + fall (int | None, optional): Порог количества ошибок. Defaults to None. + rise (int | None, optional): Порог количества успешных проверок. Defaults to None. + preset_id (int | None, optional): UID тарифа балансировщика. Defaults to None. + + Returns: + BalancerResponse: Балансировщик. + ''' + data: dict[str, str | int] = {} + if name is not None: + data['name'] = name + if algo is not None: + if isinstance(algo, schemas.BalancerAlgorithm): + data['algo'] = algo.value + else: + data['algo'] = algo + if is_sticky: + data['is_sticky'] = is_sticky + if is_use_proxy: + data['is_use_proxy'] = is_use_proxy + if is_ssl: + data['is_ssl'] = is_ssl + if is_keepalive: + data['is_keepalive'] = is_keepalive + if port is not None: + data['port'] = port + if proto is not None: + if isinstance(proto, schemas.Protocol): + data['proto'] = proto.value + else: + data['proto'] = proto + if path is not None: + data['path'] = path + if inter is not None: + data['inter'] = inter + if timeout is not None: + data['timeout'] = timeout + if fall is not None: + data['fall'] = fall + if rise is not None: + data['rise'] = rise + if preset_id is not None: + data['preset_id'] = preset_id + balancer = self._request( + 'PATCH', f'/balancers/{balancer_id}', json=data + ) + return schemas.BalancerResponse(**balancer.json()) + + def delete(self, balancer_id: int) -> bool: + '''Удалить балансировщик. + + Args: + balancer_id (int): UID балансировщика. + + Returns: + bool: Успешность удаления. + ''' + self._request( + 'DELETE', f'/balancers/{balancer_id}' + ) + return True + + def get_balancer_ips(self, balancer_id: int) -> schemas.BalancerIPsResponse: + '''Получить IP балансировщика. + + Args: + balancer_id (int): UID IP балансировщика. + + Returns: + BalancerIPsResponse: IP адреса балансировщика. + ''' + balancer_ips = self._request( + 'GET', f'/balancers/{balancer_id}/ips' + ) + return schemas.BalancerIPsResponse(**balancer_ips.json()) + + def add_balancer_ips( + self, balancer_id: int, ips: list[str | IPv4Address | IPv6Address] + ) -> bool: + '''Добавить IP адреса балансировщика. + + Args: + balancer_id (int): UID балансировщика. + ips (list[str | IPv4Address | IPv6Address]): IP адреса. + + Returns: + bool: IP адреса добавлены. + ''' + resp = self._request( + 'POST', f'/balancers/{balancer_id}/ips', json={'ips': ips} + ) + return resp.is_success + + def delete_balancer_ips( + self, balancer_id: int, ips: list[str | IPv4Address | IPv6Address] + ) -> bool: + '''Удаление IP адресов балансировщика. + + Args: + balancer_id (int): UID балансировщика. + ips (list[str | IPv4Address | IPv6Address]): IP адреса. + + Returns: + bool: IP адреса удалены. + ''' + resp = self._request( + 'DELETE', f'/balancers/{balancer_id}/ips', json={'ips': ips} + ) + return resp.is_success + + def get_balancer_rules(self, balancer_id: int) -> schemas.BalancerRulesResponse: + '''Получить правила балансировщика. + + Args: + balancer_id (int): UID балансировщика. + + Returns: + BalancerRulesResponse: Правила балансировщика. + ''' + balancer_rules = self._request( + 'GET', f'/balancers/{balancer_id}/rules' + ) + return schemas.BalancerRulesResponse(**balancer_rules.json()) + + def add_balancer_rule( + self, balancer_id: int, balancer_proto: schemas.Protocol | str, + balancer_port: int, server_proto: schemas.Protocol | str, + server_port: int + ) -> schemas.BalancerRuleResponse: + '''Добавить правило балансировщика. + + Args: + balancer_id (int): UID балансировщика. + balancer_proto (Protocol | str): Протокол балансировщика. + balancer_port (int): Порт балансировщика. + server_proto (Protocol | str): Протокол сервера. + server_port (int): Порт сервера. + + Returns: + BalancerRuleResponse: Добавленное правило. + ''' + data: dict[str, str | int] = {} + if isinstance(balancer_proto, schemas.Protocol): + data['balancer_proto'] = balancer_proto.value + else: + data['balancer_proto'] = balancer_proto + data['balancer_port'] = balancer_port + if isinstance(server_proto, schemas.Protocol): + data['server_proto'] = server_proto.value + else: + data['server_proto'] = server_proto + data['server_port'] = server_port + balancer_rule = self._request( + 'POST', f'/balancers/{balancer_id}/rules', json=data + ) + return schemas.BalancerRuleResponse(**balancer_rule.json()) + + def update_balancer_rule( + self, balancer_id: int, rule_id: int, + balancer_proto: schemas.Protocol | str, balancer_port: int, + server_proto: schemas.Protocol | str, server_port: int + ) -> schemas.BalancerRuleResponse: + '''Добавить правило балансировщика. + + Args: + balancer_id (int): UID балансировщика. + rule_id (int): UID правила. + balancer_proto (Protocol | str): Протокол балансировщика. + balancer_port (int): Порт балансировщика. + server_proto (Protocol | str): Протокол сервера. + server_port (int): Порт сервера. + + Returns: + BalancerRuleResponse: Обнавлённое правило. + ''' + data: dict[str, str | int] = {} + if isinstance(balancer_proto, schemas.Protocol): + data['balancer_proto'] = balancer_proto.value + else: + data['balancer_proto'] = balancer_proto + data['balancer_port'] = balancer_port + if isinstance(server_proto, schemas.Protocol): + data['server_proto'] = server_proto.value + else: + data['server_proto'] = server_proto + data['server_port'] = server_port + balancer_rule = self._request( + 'PATCH', f'/balancers/{balancer_id}/rules/{rule_id}', json=data + ) + return schemas.BalancerRuleResponse(**balancer_rule.json()) + + def delete_balancer_rule(self, balancer_id: int, rule_id: int) -> bool: + '''Удалить правило балансировщика. + + Args: + balancer_id (int): UID балансировщика. + rule_id (int): UID правила. + + Returns: + bool: Правило удалено. + ''' + resp = self._request( + 'DELETE', f'/balancers/{balancer_id}/rules/{rule_id}' + ) + return resp.is_success + + def get_presets(self) -> schemas.BalancerPresetsResponse: + '''Получить список тарифов балансировщиков. + + Returns: + BalancerPresetsResponse: Список тарифов. + ''' + balancer_presets = self._request('GET', '/presets/balancers') + return schemas.BalancerPresetsResponse(**balancer_presets.json())