Compare commits
2 Commits
312875ed2b
...
952d043fc9
Author | SHA1 | Date | |
---|---|---|---|
952d043fc9 | |||
4fc2116dcb |
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,3 +1,4 @@
|
|||||||
.venv/
|
.venv/
|
||||||
state/*
|
state/*
|
||||||
__pycache__
|
__pycache__
|
||||||
|
.env*
|
||||||
|
@ -21,6 +21,9 @@ class BaseSourceProvider(ABC):
|
|||||||
ipv4t,
|
ipv4t,
|
||||||
ipv6t,
|
ipv6t,
|
||||||
)
|
)
|
||||||
|
self.post_init()
|
||||||
|
|
||||||
|
def post_init(self): ...
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.__class__.__name__}: {self.name}"
|
return f"{self.__class__.__name__}: {self.name}"
|
||||||
@ -68,6 +71,9 @@ class BaseOutputProvider(ABC):
|
|||||||
def __init__(self, name, config, ipv4t, ipv6t):
|
def __init__(self, name, config, ipv4t, ipv6t):
|
||||||
self.name, self.config = name, config
|
self.name, self.config = name, config
|
||||||
self.ipv4t, self.ipv6t = ipv4t, ipv6t
|
self.ipv4t, self.ipv6t = ipv4t, ipv6t
|
||||||
|
self.post_init()
|
||||||
|
|
||||||
|
def post_init(self): ...
|
||||||
|
|
||||||
def __init_subclass__(cls) -> None:
|
def __init_subclass__(cls) -> None:
|
||||||
BaseOutputProvider._childs[cls.__name__] = cls
|
BaseOutputProvider._childs[cls.__name__] = cls
|
||||||
@ -76,10 +82,10 @@ class BaseOutputProvider(ABC):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.__class__.__name__}: {self.name}"
|
return f"{self.__class__.__name__}: {self.name}"
|
||||||
|
|
||||||
def best_client(self, addr_v4, addr_v6):
|
def best_transport(self, addr_v4, addr_v6):
|
||||||
if addr_v6 is None and addr_v4 is not None:
|
if addr_v6:
|
||||||
return self.ipv4t
|
return self.ipv6t
|
||||||
return self.ipv6t
|
return self.ipv4t
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def validate_source_config(cls, name, config):
|
def validate_source_config(cls, name, config):
|
||||||
@ -111,6 +117,9 @@ class BaseFilterProvider(ABC):
|
|||||||
def __init__(self, name, config, ipv4t, ipv6t):
|
def __init__(self, name, config, ipv4t, ipv6t):
|
||||||
self.name, self.config = name, config
|
self.name, self.config = name, config
|
||||||
self.ipv4t, self.ipv6t = ipv4t, ipv6t
|
self.ipv4t, self.ipv6t = ipv4t, ipv6t
|
||||||
|
self.post_init()
|
||||||
|
|
||||||
|
def post_init(self): ...
|
||||||
|
|
||||||
def __init_subclass__(cls) -> None:
|
def __init_subclass__(cls) -> None:
|
||||||
BaseFilterProvider._childs[cls.__name__] = cls
|
BaseFilterProvider._childs[cls.__name__] = cls
|
||||||
|
142
pddnsc/outputs/vscale.py
Normal file
142
pddnsc/outputs/vscale.py
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
import os
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from pddnsc.base import BaseOutputProvider
|
||||||
|
|
||||||
|
|
||||||
|
class VscaleDomains(BaseOutputProvider):
|
||||||
|
def post_init(self):
|
||||||
|
self.__token = self.get_api_token()
|
||||||
|
if not self.__token:
|
||||||
|
raise KeyError("no api token, use env VSCALE_API_TOKEN")
|
||||||
|
|
||||||
|
def get_api_token(self) -> str:
|
||||||
|
token_env = self.config.get("api_token_env", "VSCALE_API_TOKEN")
|
||||||
|
return os.environ[token_env]
|
||||||
|
|
||||||
|
async def find_domain_id(self, transport) -> Optional[int]:
|
||||||
|
domain = self.config["domain"]
|
||||||
|
headers = {"X-Token": self.__token}
|
||||||
|
async with httpx.AsyncClient(transport=transport) as client:
|
||||||
|
response = await client.get(
|
||||||
|
"https://api.vscale.io/v1/domains/", headers=headers
|
||||||
|
)
|
||||||
|
if httpx.codes.is_success(response.status_code):
|
||||||
|
data = response.json()
|
||||||
|
if isinstance(data, list):
|
||||||
|
for entry in data:
|
||||||
|
if entry["name"] == domain:
|
||||||
|
return entry["id"]
|
||||||
|
else:
|
||||||
|
raise TypeError(
|
||||||
|
"failed to find domain id, unexpected response type"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise ValueError(
|
||||||
|
f"failed to find domain id, code: {response.status_code}"
|
||||||
|
)
|
||||||
|
|
||||||
|
async def find_record(self, transport, domain_id, record_type) -> Optional[int]:
|
||||||
|
target = self.config["target"]
|
||||||
|
domain = self.config["domain"]
|
||||||
|
target = f"{target}.{domain}"
|
||||||
|
headers = {"X-Token": self.__token}
|
||||||
|
async with httpx.AsyncClient(transport=transport) as client:
|
||||||
|
response = await client.get(
|
||||||
|
f"https://api.vscale.io/v1/domains/{domain_id}/records/",
|
||||||
|
headers=headers,
|
||||||
|
)
|
||||||
|
if httpx.codes.is_success(response.status_code):
|
||||||
|
data = response.json()
|
||||||
|
if isinstance(data, list):
|
||||||
|
for entry in data:
|
||||||
|
if entry["name"] == target and entry["type"] == record_type:
|
||||||
|
return entry["id"]
|
||||||
|
else:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"error list records {domain_id=}: ", response.status_code
|
||||||
|
)
|
||||||
|
|
||||||
|
async def get_record_value(self, transport, domain_id, record_id) -> str:
|
||||||
|
headers = {"X-Token": self.__token}
|
||||||
|
async with httpx.AsyncClient(transport=transport) as client:
|
||||||
|
response = await client.get(
|
||||||
|
f"https://api.vscale.io/v1/domains/{domain_id}/records/{record_id}",
|
||||||
|
headers=headers,
|
||||||
|
)
|
||||||
|
if httpx.codes.is_success(response.status_code):
|
||||||
|
data = response.json()
|
||||||
|
if isinstance(data, dict):
|
||||||
|
return data["content"]
|
||||||
|
|
||||||
|
async def change_record(self, transport, domain_id, record_id, record_type, value):
|
||||||
|
target = self.config["target"]
|
||||||
|
domain = self.config["domain"]
|
||||||
|
target = f"{target}.{domain}"
|
||||||
|
ttl = self.config.get("ttl", 300)
|
||||||
|
headers = {"X-Token": self.__token, "Content-Type": "application/json"}
|
||||||
|
data = {
|
||||||
|
"content": value,
|
||||||
|
"name": target,
|
||||||
|
"ttl": ttl,
|
||||||
|
"type": record_type,
|
||||||
|
"id": record_id,
|
||||||
|
}
|
||||||
|
async with httpx.AsyncClient(transport=transport) as client:
|
||||||
|
response = await client.put(
|
||||||
|
f"https://api.vscale.io/v1/domains/{domain_id}/records/{record_id}",
|
||||||
|
headers=headers,
|
||||||
|
json=data,
|
||||||
|
)
|
||||||
|
if not httpx.codes.is_success(response.status_code):
|
||||||
|
raise RuntimeError(
|
||||||
|
f"failed to change record: {target=},{domain_id=}, {record_id=}, {record_type=}, {value=}"
|
||||||
|
)
|
||||||
|
|
||||||
|
async def create_record(self, transport, domain_id, record_type, value):
|
||||||
|
target = self.config["target"]
|
||||||
|
domain = self.config["domain"]
|
||||||
|
target = f"{target}.{domain}"
|
||||||
|
ttl = self.config.get("ttl", 300)
|
||||||
|
headers = {"X-Token": self.__token, "Content-Type": "application/json"}
|
||||||
|
data = {"content": value, "name": target, "ttl": ttl, "type": record_type}
|
||||||
|
async with httpx.AsyncClient(transport=transport) as client:
|
||||||
|
response = await client.post(
|
||||||
|
f"https://api.vscale.io/v1/domains/{domain_id}/records/",
|
||||||
|
headers=headers,
|
||||||
|
json=data,
|
||||||
|
)
|
||||||
|
if not httpx.codes.is_success(response.status_code):
|
||||||
|
raise RuntimeError(
|
||||||
|
f"failed to create record: {target=},{domain_id=}, {record_type=}, {value=}, {response.status_code=}"
|
||||||
|
)
|
||||||
|
|
||||||
|
async def set_addrs_imp(self, source_provider, addr_v4, addr_v6):
|
||||||
|
transport = self.best_transport(addr_v4, addr_v6)
|
||||||
|
domain_id = await self.find_domain_id(transport)
|
||||||
|
save_ipv4 = self.config.get("ipv4", False)
|
||||||
|
save_ipv6 = self.config.get("ipv6", False)
|
||||||
|
if "ipv4" not in self.config and "ipv6" not in self.config:
|
||||||
|
save_ipv4 = save_ipv6 = True
|
||||||
|
|
||||||
|
save_addrs = []
|
||||||
|
if addr_v4 and save_ipv4:
|
||||||
|
save_addrs.append(("A", addr_v4))
|
||||||
|
|
||||||
|
if addr_v6 and save_ipv6:
|
||||||
|
save_addrs.append(("AAAA", addr_v6))
|
||||||
|
|
||||||
|
for record_type, value in save_addrs:
|
||||||
|
record_id = await self.find_record(transport, domain_id, record_type)
|
||||||
|
if record_id:
|
||||||
|
old_value = await self.get_record_value(transport, domain_id, record_id)
|
||||||
|
if old_value != value:
|
||||||
|
await self.change_record(
|
||||||
|
transport, domain_id, record_id, record_type, value
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
print(f"vscale: skip record change ({record_type=}), value equal")
|
||||||
|
else:
|
||||||
|
await self.create_record(transport, domain_id, record_type, value)
|
Loading…
x
Reference in New Issue
Block a user