diff options
| author | Panu Oksiala <panu@oksiala.fi> | 2024-01-22 00:24:37 +0200 |
|---|---|---|
| committer | Panu Oksiala <panu@oksiala.fi> | 2024-01-22 00:24:37 +0200 |
| commit | 73c1115e6fba24b86eca1e5cad5f5086d6bd2ba6 (patch) | |
| tree | daedbc44f41c91a13cb7fc26edd325ab14996217 /desmo_api/hcloud_dns.py | |
Initial commit
Diffstat (limited to 'desmo_api/hcloud_dns.py')
| -rw-r--r-- | desmo_api/hcloud_dns.py | 146 |
1 files changed, 146 insertions, 0 deletions
diff --git a/desmo_api/hcloud_dns.py b/desmo_api/hcloud_dns.py new file mode 100644 index 0000000..e8b6a3a --- /dev/null +++ b/desmo_api/hcloud_dns.py @@ -0,0 +1,146 @@ +import asyncio +import aiohttp +from typing import Dict, List, Optional + +from pydantic import BaseModel + + +class TxtVerification(BaseModel): + name: str + token: str + + +class ZoneResponse(BaseModel): + id: str + created: str + modified: str + legacy_dns_host: str + legacy_ns: List[str] + name: str + ns: List[str] + owner: str + paused: bool + permission: str + project: str + registrar: str + status: str + ttl: int + verified: str + records_count: int + is_secondary_dns: bool + txt_verification: TxtVerification + + +class RecordResponse(BaseModel): + type: str + id: str + created: str + modified: str + zone_id: str + name: str + value: str + ttl: int | None = None + + +class HCloudDNS: + def __init__(self, token: str, api_domain="dns.hetzner.com"): + self._token = token + self.api_domain = api_domain + self._session: Optional[aiohttp.ClientSession] = None + + def _get_session(self): + if self._session is None: + self._session = aiohttp.ClientSession( + headers={ + "Auth-API-Token": self._token, + "Content-Type": "application/json; charset=utf-8", + } + ) + return self._session + + async def close(self): + if self._session is not None: + await self._session.close() + await asyncio.sleep(0.250) + + async def get_all_zones(self, name: Optional[str] = None) -> List[ZoneResponse]: + session = self._get_session() + params = {} + if name is not None: + params["name"] = name + async with session.get( + f"https://{ self.api_domain }/api/v1/zones", params=params + ) as resp: + resp.raise_for_status() + return [ZoneResponse(**zone) for zone in (await resp.json())["zones"]] + + async def get_zone(self, zone_id: str) -> ZoneResponse: + session = self._get_session() + async with session.get( + f"https://{ self.api_domain }/api/v1/zones/{zone_id}" + ) as resp: + resp.raise_for_status() + return ZoneResponse(**(await resp.json())["zone"]) + + async def get_zone_by_name(self, name: str) -> ZoneResponse: + zones = await self.get_all_zones(name) + if len(zones) == 0: + raise ValueError(f"Zone '{name}' not found") + elif len(zones) > 1: + raise ValueError(f"Multiple zones found for '{name}'") + else: + return zones[0] + + async def get_all_records(self, zone_id: str) -> List[RecordResponse]: + session = self._get_session() + async with session.get( + f"https://{ self.api_domain }/api/v1/records", params={"zone_id": zone_id} + ) as resp: + resp.raise_for_status() + data = await resp.json() + return [RecordResponse(**record) for record in data["records"]] + + async def get_record(self, record_id: str) -> RecordResponse: + session = self._get_session() + async with session.get( + f"https://{ self.api_domain }/api/v1/records/{record_id}" + ) as resp: + resp.raise_for_status() + return RecordResponse(**(await resp.json())["record"]) + + async def get_records_by_name( + self, zone_id: str, name: str + ) -> List[RecordResponse]: + records = await self.get_all_records(zone_id) + return [record for record in records if record.name == name] + + async def delete_record(self, record_id: str) -> None: + session = self._get_session() + async with session.delete( + f"https://{ self.api_domain }/api/v1/records/{record_id}" + ) as resp: + resp.raise_for_status() + return None + + async def create_record( + self, + zone_id: str, + name: str, + record_type: str, + value: str, + ttl: Optional[int] = None, + ): + session = self._get_session() + data: Dict[str, str | int] = { + "name": name, + "type": record_type, + "value": value, + "zone_id": zone_id, + } + if ttl is not None: + data["ttl"] = ttl + async with session.post( + f"https://{ self.api_domain }/api/v1/records", json=data + ) as resp: + resp.raise_for_status() + return RecordResponse(**(await resp.json())["record"]) |
