use std::str::FromStr; use std::time::Instant; use lmrc_cloudflare::{CloudflareClient}; use crate::{ClientNotInitializedError, DnsProvider, DnsRecord, RecordType}; #[derive(Debug)] pub struct CloudflareDnsProvider { api_key: String, zone_id: String, client: Option } impl CloudflareDnsProvider { pub fn new(api_key: String, zone_id: String) -> anyhow::Result { Ok(Self { client: Some(CloudflareClient::new(&api_key)?), api_key, zone_id }) } fn cloudflare_records_to_generic(&self, records: Vec) -> anyhow::Result>>> { if records.len() == 0 { Err(anyhow::anyhow!("DNS record not found")) } else { Ok(records.into_iter().map(|rec| CloudflareDnsRecord::from_record(self, rec) )) } } } impl<'a> DnsProvider<'a> for CloudflareDnsProvider { type Client = CloudflareClient; type Record = CloudflareDnsRecord<'a>; fn init_client(&mut self) -> anyhow::Result<()> { if let None = self.client { self.client = Some(CloudflareClient::new(&self.api_key)?); } Ok(()) } fn get_client(&self) -> Result<&CloudflareClient, ClientNotInitializedError> { self.client.as_ref().ok_or(ClientNotInitializedError {message: "Client not initialized".to_string()}) } async fn get_dns_records_by_name(&'a self, name: impl Into) -> anyhow::Result>> { let client = self.get_client()?; let dns = client.dns(); let records = dns.list_records(&self.zone_id).name(name.into()).send().await?; self.cloudflare_records_to_generic(records) } async fn get_dns_records_by_ip(&'a self, ip: impl Into) -> anyhow::Result>> { let client = self.get_client()?; let dns = client.dns(); let records = dns.list_records(&self.zone_id).content(ip.into()).send().await?; self.cloudflare_records_to_generic(records) } async fn get_dns_records(&'a self) -> anyhow::Result>> { let client = self.get_client()?; let dns = client.dns(); let records = dns.list_records(&self.zone_id).send().await?; self.cloudflare_records_to_generic(records) } async fn get_dns_record_by_id(&'a self, id: &str) -> anyhow::Result { let client = self.get_client()?; let dns = client.dns(); match dns.get_record(&self.zone_id, id).await { Ok(record) => { Ok(CloudflareDnsRecord::from_record(self, record)?) } Err(e) => { Err(anyhow::anyhow!("Failed to get DNS record: {}", e)) } } } async fn create_dns_record(&'a self, subdomain: &str, target: &str, record_type: RecordType, ttl: Option) -> anyhow::Result { let client = self.get_client()?; let dns = client.dns(); match dns.create_record(&self.zone_id) .name(subdomain) .content(target) .record_type(record_type.into()) .ttl(ttl.unwrap_or(1)) .send().await { Ok(record) => { Ok(CloudflareDnsRecord::from_record(self, record)?) } Err(e) => { Err(anyhow::anyhow!("Failed to get DNS record: {}", e)) } } } } pub struct CloudflareDnsRecord<'a> { provider: &'a CloudflareDnsProvider, record_id: String, name: String, target: String, record_type: RecordType, ttl: u32, priority: Option, last_refresh: Instant } impl<'a> CloudflareDnsRecord<'a> { fn from_record(provider: &'a CloudflareDnsProvider, record: lmrc_cloudflare::DnsRecord) -> anyhow::Result { Ok( CloudflareDnsRecord { provider, record_id: record.id, name: record.name, target: record.content, record_type: RecordType::from_str(&record.record_type)?, ttl: record.ttl, priority: record.priority, last_refresh: Instant::now(), } ) } } impl<'a> DnsRecord<'a> for CloudflareDnsRecord<'a> { type Client = CloudflareClient; type Provider = CloudflareDnsProvider; fn get_provider(&self) -> &Self::Provider { self.provider } async fn name(&mut self) -> anyhow::Result { if self.last_refresh.elapsed().as_secs() > 300 { self.refresh().await?; } Ok(self.name.clone()) } async fn target(&mut self) -> anyhow::Result { if self.last_refresh.elapsed().as_secs() > 300 { self.refresh().await?; } Ok(self.target.clone()) } async fn ttl(&mut self) -> anyhow::Result { if self.last_refresh.elapsed().as_secs() > 300 { self.refresh().await?; } Ok(self.ttl) } async fn record_type(&mut self) -> anyhow::Result { if self.last_refresh.elapsed().as_secs() > 300 { self.refresh().await?; } Ok(self.record_type) } async fn is_valid(&self) -> anyhow::Result { if self.provider.get_client()?.dns().get_record(&self.provider.zone_id, &self.record_id).await.is_err() { Ok(false) } else { Ok(true) } } async fn priority(&mut self) -> anyhow::Result> { if self.last_refresh.elapsed().as_secs() > 300 { self.refresh().await?; } Ok(self.priority) } async fn set_target(&mut self, ip: &str) -> anyhow::Result<()> { self.refresh().await?; self.provider.get_client()?.dns().update_record( &self.provider.zone_id, &self.record_id ).content(ip).send().await?; self.refresh().await?; Ok(()) } async fn set_ttl(&mut self, ttl: u32) -> anyhow::Result<()> { self.refresh().await?; self.provider.get_client()?.dns().update_record( &self.provider.zone_id, &self.record_id ).ttl(ttl).send().await?; self.refresh().await?; Ok(()) } async fn set_record_type(&mut self, record_type: RecordType) -> anyhow::Result<()> { self.refresh().await?; self.provider.get_client()?.dns().update_record( &self.provider.zone_id, &self.record_id ).record_type(record_type.into()).send().await?; self.refresh().await?; Ok(()) } async fn set_priority(&mut self, priority: u16) -> anyhow::Result<()> { self.refresh().await?; self.provider.get_client()?.dns().update_record( &self.provider.zone_id, &self.record_id ).priority(priority).send().await?; self.refresh().await?; Ok(()) } async fn delete(&mut self) -> anyhow::Result<()> { self.refresh().await?; self.provider.get_client()?.dns().delete_record(&self.provider.zone_id, &self.record_id).await?; Ok(()) } async fn refresh(&mut self) -> anyhow::Result<()> { let client = self.provider.get_client()?; let dns = client.dns(); let record = match dns.get_record(&self.provider.zone_id, &self.record_id).await { Ok(record) => {Ok(record)} Err(_) => { match dns.find_record(&self.provider.zone_id, &self.name, self.record_type.into()).await? { Some(record) => Ok(record), None => { match dns.list_records(&self.provider.zone_id).name(&self.name).send().await { Ok(records) => { let mut record = None; for r in records { if r.name == self.name && r.content == self.target { record = Some(r); } } if let Some(record) = record { Ok(record) } else { Err(anyhow::anyhow!("DNS record not found")) } } Err(_) => Err(anyhow::anyhow!("Failed to list DNS records")) } } } } }?; self.record_id = record.id; self.name = record.name; self.target = record.content; self.priority = record.priority; self.ttl = record.ttl; self.record_type = RecordType::from_str(&record.record_type)?; self.last_refresh = Instant::now(); Ok(()) } } impl Into for RecordType { fn into(self) -> lmrc_cloudflare::RecordType { match self { RecordType::A => lmrc_cloudflare::RecordType::A, RecordType::AAAA => lmrc_cloudflare::RecordType::AAAA, RecordType::CNAME => lmrc_cloudflare::RecordType::CNAME, RecordType::MX => lmrc_cloudflare::RecordType::MX, RecordType::TXT => lmrc_cloudflare::RecordType::TXT, RecordType::SRV => lmrc_cloudflare::RecordType::SRV, RecordType::NS => lmrc_cloudflare::RecordType::NS, RecordType::CAA => lmrc_cloudflare::RecordType::CAA, RecordType::PTR => lmrc_cloudflare::RecordType::PTR, RecordType::DNSKEY => lmrc_cloudflare::RecordType::DNSKEY, RecordType::DS => lmrc_cloudflare::RecordType::DS, RecordType::HTTPS => lmrc_cloudflare::RecordType::HTTPS, RecordType::LOC => lmrc_cloudflare::RecordType::LOC, RecordType::NAPTR => lmrc_cloudflare::RecordType::NAPTR, RecordType::SMIMEA => lmrc_cloudflare::RecordType::SMIMEA, RecordType::SSHFP => lmrc_cloudflare::RecordType::SSHFP, RecordType::SVCB => lmrc_cloudflare::RecordType::SVCB, RecordType::TLSA => lmrc_cloudflare::RecordType::TLSA, RecordType::URI => lmrc_cloudflare::RecordType::URI } } }