This small application can be used to update the value of a DNS record.
at main 88 lines 2.8 kB view raw
1"""Module with the code to update a DNS record.""" 2 3import logging 4 5import httpx 6from pydantic import SecretStr 7from pydantic_settings import BaseSettings, SettingsConfigDict 8 9logger = logging.getLogger(__name__) 10 11 12class Settings(BaseSettings): 13 """Configuration of the updater.""" 14 15 digital_ocean_token: SecretStr 16 17 model_config = SettingsConfigDict(env_prefix="dyns_") 18 19 20def public_ip() -> str: 21 """Get the public IP of the host. 22 23 It uses https://www.ipify.org/. 24 """ 25 response = httpx.get("https://api.ipify.org?format=json") 26 response.raise_for_status() 27 data = response.json() 28 logger.debug(f"Public IP: {str(data)}") 29 return response.json()["ip"] 30 31 32def digital_ocean_client() -> httpx.Client: 33 """Create a httpx client with parameters for DigitalOcean.""" 34 settings = Settings() 35 token = settings.digital_ocean_token.get_secret_value() 36 37 base_url = "https://api.digitalocean.com/v2" 38 headers = {"Authorization": f"Bearer {token}"} 39 40 return httpx.Client(base_url=base_url, headers=headers) 41 42 43def retrieve_record(name: str, domain: str) -> dict[str, str | int | None] | None: 44 """Retrieve the record for the domain and name.""" 45 with digital_ocean_client() as do_client: 46 # looks for the record ID using the list endpoint 47 # ------------------------------------------------------------------------------ 48 response = do_client.get(f"/domains/{domain}/records") 49 response.raise_for_status() 50 51 data = response.json() 52 domain_records = data["domain_records"] 53 54 # handle pagination 55 next = data["links"]["pages"].get("next") 56 while next: 57 response = do_client.get(next) 58 response.raise_for_status() 59 data = response.json() 60 domain_records += data["domain_records"] 61 next = data["links"]["pages"].get("next") 62 63 # filter the results 64 records = [record for record in domain_records if record["name"] == name] 65 if not records: 66 logger.error(f"Record {name} not found in domain {domain}") 67 return None 68 record = records[0] 69 70 return record 71 72 73def updater(name: str, domain: str) -> None: 74 """Update the DNS record using the DigitalOcean API.""" 75 record = retrieve_record(name=name, domain=domain) 76 if not record: 77 msg = f"Record {name}.{domain} not found" 78 logger.error(msg) 79 raise ValueError(msg) 80 81 with digital_ocean_client() as do_client: 82 ip = public_ip() 83 payload = {"type": "A", "data": ip} 84 response = do_client.patch( 85 f"/domains/{domain}/records/{record['id']}", json=payload 86 ) 87 response.raise_for_status() 88 logger.info(f"DNS record updated with '{ip}'")