use log::{debug, info};
use reqwest::Client as Reqw;
use std::net::IpAddr;
pub struct Dynu {
token: String,
client: Reqw,
}
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct DynuRecord {
id: u32,
domain_id: u32,
ipv4_address: IpAddr,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
struct DynuRecordsResp {
dns_records: Vec<DynuRecord>,
}
#[derive(Deserialize, Debug)]
struct DynuGetRoot {
id: u32,
}
#[derive(Serialize, Debug)]
#[serde(rename_all = "camelCase")]
struct DynuAddPayload {
group: String,
ipv4_address: IpAddr,
node_name: String,
record_type: String,
state: bool,
ttl: u32,
}
fn extract_record_name(fqdn: &str, zone: &str) -> String {
fqdn[..(fqdn.len() - zone.len() - 1)].to_owned()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_extract_record_name() {
assert_eq!(extract_record_name("a.b.c.d", "b.c.d"), "a");
assert_eq!(extract_record_name("aaa.b.c.d", "b.c.d"), "aaa");
}
}
impl Dynu {
pub fn new(token: String) -> Self {
let client = Reqw::builder()
.user_agent("rrdnsd")
.pool_max_idle_per_host(0)
.build()
.unwrap();
Dynu { token, client }
}
pub async fn add_record(&self, fqdn: &str, zone: &str, ipaddr: IpAddr) {
debug!("[dynu] Fetching domain ID for {fqdn}");
let domain_id = {
let url = format!("https://api.dynu.com/v2/dns/getroot/{fqdn}");
let res = self
.client
.get(url)
.header("API-Key", self.token.clone())
.send()
.await
.unwrap();
let r = res.json::<DynuGetRoot>().await.unwrap();
r.id
};
debug!("[dynu] Received domain ID {domain_id}");
let url = format!("https://api.dynu.com/v2/dns/{domain_id}/record");
let payload = DynuAddPayload {
group: String::new(),
ipv4_address: ipaddr,
node_name: extract_record_name(fqdn, zone),
record_type: "A".to_owned(),
state: true,
ttl: 30,
};
debug!("[dynu] Adding record domain ID {domain_id}");
let res = self
.client
.post(url)
.header("API-Key", self.token.clone())
.json(&payload)
.send()
.await
.unwrap();
let qq = res.text().await.unwrap();
let v: serde_json::Value = serde_json::from_str(&qq).unwrap();
info!("{:?}", v);
}
pub async fn delete_record(&self, fqdn: &str, _zone: &str, ipaddr: IpAddr) {
let existing_record: DynuRecord = 'found: {
let url = format!("https://api.dynu.com/v2/dns/record/{fqdn}?recordType=A");
let res = self
.client
.get(url)
.header("API-Key", self.token.clone())
.send()
.await
.unwrap();
let j = res.text().await.unwrap();
let r: DynuRecordsResp = serde_json::from_str(&j).unwrap();
for rdata in r.dns_records {
if rdata.ipv4_address == ipaddr {
break 'found rdata;
}
}
info!("not found");
return;
};
debug!("Found record {:?}", existing_record);
let url = format!(
"https://api.dynu.com/v2/dns/{}/record/{}",
existing_record.domain_id, existing_record.id
);
let res = self
.client
.delete(url.clone())
.header("API-Key", self.token.clone())
.header("accept", "application/json")
.header("Content-Type", "application/json")
.send()
.await
.unwrap();
let qq = res.text().await.unwrap();
let v: serde_json::Value = serde_json::from_str(&qq).unwrap();
info!("{:?}", v);
}
}