291 lines
10 KiB
Rust
291 lines
10 KiB
Rust
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<CloudflareClient>
|
|
}
|
|
|
|
impl CloudflareDnsProvider {
|
|
pub fn new(api_key: String, zone_id: String) -> anyhow::Result<Self> {
|
|
Ok(Self {
|
|
client: Some(CloudflareClient::new(&api_key)?), api_key, zone_id
|
|
})
|
|
}
|
|
|
|
fn cloudflare_records_to_generic(&self, records: Vec<lmrc_cloudflare::DnsRecord>) -> anyhow::Result<impl IntoIterator<Item=anyhow::Result<CloudflareDnsRecord<'_>>>> {
|
|
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<String>) -> anyhow::Result<impl IntoIterator<Item=anyhow::Result<Self::Record>>> {
|
|
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<String>) -> anyhow::Result<impl IntoIterator<Item = anyhow::Result<Self::Record>>> {
|
|
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<impl IntoIterator<Item = anyhow::Result<Self::Record>>> {
|
|
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<Self::Record> {
|
|
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<u32>) -> anyhow::Result<Self::Record> {
|
|
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<u16>,
|
|
last_refresh: Instant
|
|
}
|
|
|
|
impl<'a> CloudflareDnsRecord<'a> {
|
|
fn from_record(provider: &'a CloudflareDnsProvider, record: lmrc_cloudflare::DnsRecord) -> anyhow::Result<Self>
|
|
{
|
|
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<String> {
|
|
if self.last_refresh.elapsed().as_secs() > 300 {
|
|
self.refresh().await?;
|
|
}
|
|
Ok(self.name.clone())
|
|
}
|
|
|
|
async fn target(&mut self) -> anyhow::Result<String> {
|
|
if self.last_refresh.elapsed().as_secs() > 300 {
|
|
self.refresh().await?;
|
|
}
|
|
Ok(self.target.clone())
|
|
}
|
|
|
|
async fn ttl(&mut self) -> anyhow::Result<u32> {
|
|
if self.last_refresh.elapsed().as_secs() > 300 {
|
|
self.refresh().await?;
|
|
}
|
|
Ok(self.ttl)
|
|
}
|
|
|
|
async fn record_type(&mut self) -> anyhow::Result<RecordType> {
|
|
if self.last_refresh.elapsed().as_secs() > 300 {
|
|
self.refresh().await?;
|
|
}
|
|
Ok(self.record_type)
|
|
}
|
|
|
|
async fn is_valid(&self) -> anyhow::Result<bool> {
|
|
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<Option<u16>> {
|
|
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<lmrc_cloudflare::RecordType> 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
|
|
}
|
|
}
|
|
} |