Skip to content

Commit 29f6337

Browse files
committed
Refactor Cloudflare provider into separate functions
1 parent 588ea4e commit 29f6337

File tree

1 file changed

+157
-98
lines changed

1 file changed

+157
-98
lines changed

src/providers/cloudflare.rs

Lines changed: 157 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
use std::net::IpAddr;
2+
13
use anyhow::{anyhow, Result};
24
use async_trait::async_trait;
35
use reqwest::Client;
46
use serde::{Deserialize, Serialize};
5-
use serde_json::{json, Value};
7+
use serde_json::json;
68
use smallvec::SmallVec;
79

810
use crate::client::{IpUpdate, IpVersion, Provider};
@@ -12,7 +14,7 @@ use crate::client::{IpUpdate, IpVersion, Provider};
1214
pub struct Cloudflare {
1315
zone: String,
1416
api_token: String,
15-
domains: SmallVec<[Domains; 2]>,
17+
domains: SmallVec<[Domain; 2]>,
1618
#[serde(default = "default_api_url")]
1719
api_url: String,
1820
}
@@ -23,32 +25,38 @@ fn default_api_url() -> String {
2325

2426
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
2527
#[serde(default)]
26-
struct Domains {
28+
struct Domain {
2729
name: String,
30+
#[serde(default = "default_ttl")]
2831
ttl: u32,
32+
#[serde(default)]
2933
proxied: bool,
30-
comment: Option<String>,
34+
#[serde(default = "default_comment")]
35+
comment: String,
36+
}
37+
38+
// TTL of 1 is Cloudflare's auto setting
39+
fn default_ttl() -> u32 {
40+
1
41+
}
42+
43+
fn default_comment() -> String {
44+
String::from("Created by DDRS")
3145
}
3246

33-
/// Zone lookup response
3447
#[derive(Debug, Deserialize)]
3548
struct ZoneList {
3649
result: Option<Vec<ZoneResult>>,
37-
#[serde(rename = "errors")]
38-
_errors: Vec<Value>,
3950
}
4051

4152
#[derive(Debug, Deserialize)]
4253
struct ZoneResult {
4354
id: String,
4455
}
4556

46-
/// Records lookup response
4757
#[derive(Debug, Deserialize)]
4858
struct RecordsList {
4959
result: Option<Vec<RecordResult>>,
50-
#[serde(rename = "errors")]
51-
_errors: Vec<Value>,
5260
}
5361

5462
#[derive(Debug, Deserialize)]
@@ -58,26 +66,16 @@ struct RecordResult {
5866

5967
#[derive(Debug, Deserialize)]
6068
struct UpdatedResult {
61-
#[serde(rename = "errors")]
62-
_errors: Vec<Value>,
63-
#[serde(rename = "messages")]
64-
_messages: Vec<Value>,
6569
success: bool,
6670
}
6771

6872
#[derive(Debug, Deserialize)]
6973
struct CreatedResult {
70-
#[serde(rename = "errors")]
71-
_errors: Vec<Value>,
72-
#[serde(rename = "messages")]
73-
_messages: Vec<Value>,
7474
success: bool,
7575
}
7676

77-
#[async_trait]
78-
#[typetag::serde(name = "cloudflare")]
79-
impl Provider for Cloudflare {
80-
async fn update(&self, update: IpUpdate, request: Client) -> Result<bool> {
77+
impl Cloudflare {
78+
async fn fetch_zone_id(&self, request: &Client) -> Result<String> {
8179
let zones = request
8280
.get(format!("{}/zones", self.api_url))
8381
.query(&[("name", &self.zone)])
@@ -89,75 +87,136 @@ impl Provider for Cloudflare {
8987
let zone_result = zones.result.ok_or(anyhow!(
9088
"Failed to list Cloudflare zones, is your token valid?"
9189
))?;
92-
let zone_id = &zone_result
90+
Ok(zone_result
9391
.first()
9492
.ok_or(anyhow!("Failed to find a matching Cloudflare zone"))?
95-
.id;
93+
.id
94+
.clone())
95+
}
96+
97+
async fn fetch_dns_records(
98+
&self,
99+
request: &Client,
100+
zone_id: &str,
101+
record_type: &str,
102+
domain: &Domain,
103+
) -> Result<Vec<RecordResult>> {
104+
let records = request
105+
.get(format!("{}/zones/{}/dns_records", self.api_url, zone_id))
106+
.query(&[("name", &domain.name)])
107+
.query(&[("type", record_type)])
108+
.bearer_auth(&self.api_token)
109+
.send()
110+
.await?
111+
.json::<RecordsList>()
112+
.await?;
113+
records.result.ok_or(anyhow!(
114+
"Failed to list Cloudflare DNS records for {}",
115+
domain.name
116+
))
117+
}
118+
119+
async fn update_dns_record(
120+
&self,
121+
request: &Client,
122+
zone_id: &str,
123+
record_id: &str,
124+
record_type: &str,
125+
domain: &Domain,
126+
address: &IpAddr,
127+
) -> Result<()> {
128+
let updated = request
129+
.put(format!(
130+
"{}/zones/{}/dns_records/{}",
131+
self.api_url, zone_id, record_id,
132+
))
133+
.json(&json!({
134+
"type": record_type,
135+
"name": domain,
136+
"content": address,
137+
"ttl": domain.ttl,
138+
"proxied": domain.proxied,
139+
"comment": domain.comment,
140+
}))
141+
.bearer_auth(&self.api_token)
142+
.send()
143+
.await?
144+
.json::<UpdatedResult>()
145+
.await?;
146+
if !updated.success {
147+
return Err(anyhow!(
148+
"Failed to update Cloudflare domain ({}) record",
149+
domain.name
150+
));
151+
}
152+
Ok(())
153+
}
154+
155+
async fn create_dns_record(
156+
&self,
157+
request: &Client,
158+
zone_id: &str,
159+
record_type: &str,
160+
domain: &Domain,
161+
address: &IpAddr,
162+
) -> Result<()> {
163+
let created = request
164+
.post(format!("{}/zones/{}/dns_records", self.api_url, zone_id))
165+
.json(&json!({
166+
"type": record_type,
167+
"name": domain,
168+
"content": address,
169+
"ttl": domain.ttl,
170+
"proxied": domain.proxied,
171+
"comment": domain.comment,
172+
}))
173+
.bearer_auth(&self.api_token)
174+
.send()
175+
.await?
176+
.json::<CreatedResult>()
177+
.await?;
178+
if !created.success {
179+
return Err(anyhow!(
180+
"Failed to create Cloudflare domain ({}) record",
181+
domain.name
182+
));
183+
}
184+
Ok(())
185+
}
186+
}
187+
188+
#[async_trait]
189+
#[typetag::serde(name = "cloudflare")]
190+
impl Provider for Cloudflare {
191+
async fn update(&self, update: IpUpdate, request: Client) -> Result<bool> {
192+
let zone_id = self.fetch_zone_id(&request).await?;
96193
for domain in &self.domains {
97194
for (version, address) in update.as_array() {
98-
if address.is_none() {
99-
continue;
100-
}
101-
let record_type = match version {
102-
IpVersion::V4 => "A",
103-
IpVersion::V6 => "AAAA",
104-
};
105-
let records = request
106-
.get(format!("{}/zones/{zone_id}/dns_records", self.api_url))
107-
.query(&[("name", &domain.name)])
108-
.query(&[("type", record_type)])
109-
.bearer_auth(&self.api_token)
110-
.send()
111-
.await?
112-
.json::<RecordsList>()
113-
.await?;
114-
if let Some(record) = records.result.and_then(|vec| vec.into_iter().next()) {
115-
let updated = request
116-
.put(format!(
117-
"{}/zones/{zone_id}/dns_records/{}",
118-
self.api_url, record.id,
119-
))
120-
.json(&json!({
121-
"type": record_type,
122-
"name": domain.name,
123-
"content": address,
124-
"ttl": domain.ttl,
125-
"proxied": domain.proxied,
126-
"comment": domain.comment,
127-
}))
128-
.bearer_auth(&self.api_token)
129-
.send()
195+
if let Some(addr) = address {
196+
let record_type = match version {
197+
IpVersion::V4 => "A",
198+
IpVersion::V6 => "AAAA",
199+
};
200+
if let Some(record) = self
201+
.fetch_dns_records(&request, &zone_id, record_type, domain)
130202
.await?
131-
.json::<UpdatedResult>()
203+
.first()
204+
{
205+
println!("Updating record: {}", record.id);
206+
self.update_dns_record(
207+
&request,
208+
&zone_id,
209+
&record.id,
210+
record_type,
211+
domain,
212+
&addr,
213+
)
132214
.await?;
133-
if !updated.success {
134-
return Err(anyhow!(
135-
"Failed to update Cloudflare domain ({}) record",
136-
domain.name
137-
));
215+
} else {
216+
println!("Creating record");
217+
self.create_dns_record(&request, &zone_id, record_type, domain, &addr)
218+
.await?;
138219
}
139-
continue;
140-
}
141-
let created = request
142-
.post(format!("{}/zones/{zone_id}/dns_records", self.api_url))
143-
.json(&json!({
144-
"type": record_type,
145-
"name": domain.name,
146-
"content": address,
147-
"ttl": domain.ttl,
148-
"proxied": domain.proxied,
149-
"comment": domain.comment,
150-
}))
151-
.bearer_auth(&self.api_token)
152-
.send()
153-
.await?
154-
.json::<CreatedResult>()
155-
.await?;
156-
if !created.success {
157-
return Err(anyhow!(
158-
"Failed to create Cloudflare domain ({}) record",
159-
domain.name
160-
));
161220
}
162221
}
163222
}
@@ -197,11 +256,11 @@ mod tests {
197256
let provider = Cloudflare {
198257
zone: "example.com".to_string(),
199258
api_token: "bad_token".to_string(),
200-
domains: smallvec![Domains {
259+
domains: smallvec![Domain {
201260
name: "example.com".to_string(),
202261
ttl: 1,
203262
proxied: true,
204-
comment: None,
263+
comment: "Created by DDRS".to_string(),
205264
}],
206265
api_url: mock.uri(),
207266
};
@@ -241,11 +300,11 @@ mod tests {
241300
let provider = Cloudflare {
242301
zone: "example.com".to_string(),
243302
api_token: "token".to_string(),
244-
domains: smallvec![Domains {
303+
domains: smallvec![Domain {
245304
name: "example.com".to_string(),
246305
ttl: 1,
247306
proxied: true,
248-
comment: None,
307+
comment: "Created by DDRS".to_string(),
249308
}],
250309
api_url: mock.uri(),
251310
};
@@ -316,11 +375,11 @@ mod tests {
316375
let provider = Cloudflare {
317376
zone: "example.com".to_string(),
318377
api_token: "token".to_string(),
319-
domains: smallvec![Domains {
378+
domains: smallvec![Domain {
320379
name: "example.com".to_string(),
321380
ttl: 1,
322381
proxied: true,
323-
comment: Some("Created by DDRS".to_string()),
382+
comment: "Created by DDRS".to_string(),
324383
}],
325384
api_url: mock.uri(),
326385
};
@@ -426,11 +485,11 @@ mod tests {
426485
let provider = Cloudflare {
427486
zone: "example.com".to_string(),
428487
api_token: "token".to_string(),
429-
domains: smallvec![Domains {
488+
domains: smallvec![Domain {
430489
name: "example.com".to_string(),
431490
ttl: 1,
432491
proxied: true,
433-
comment: Some("Created by DDRS".to_string()),
492+
comment: "Created by DDRS".to_string(),
434493
}],
435494
api_url: mock.uri(),
436495
};
@@ -503,11 +562,11 @@ mod tests {
503562
let provider = Cloudflare {
504563
zone: "example.com".to_string(),
505564
api_token: "token".to_string(),
506-
domains: smallvec![Domains {
565+
domains: smallvec![Domain {
507566
name: "example.com".to_string(),
508567
ttl: 1,
509568
proxied: true,
510-
comment: Some("Created by DDRS".to_string()),
569+
comment: "Created by DDRS".to_string(),
511570
}],
512571
api_url: mock.uri(),
513572
};
@@ -582,11 +641,11 @@ mod tests {
582641
let provider = Cloudflare {
583642
zone: "example.com".to_string(),
584643
api_token: "token".to_string(),
585-
domains: smallvec![Domains {
644+
domains: smallvec![Domain {
586645
name: "example.com".to_string(),
587646
ttl: 1,
588647
proxied: true,
589-
comment: Some("Created by DDRS".to_string()),
648+
comment: "Created by DDRS".to_string(),
590649
}],
591650
api_url: mock.uri(),
592651
};
@@ -672,11 +731,11 @@ mod tests {
672731
let provider = Cloudflare {
673732
zone: "example.com".to_string(),
674733
api_token: "token".to_string(),
675-
domains: smallvec![Domains {
734+
domains: smallvec![Domain {
676735
name: "example.com".to_string(),
677736
ttl: 1,
678737
proxied: true,
679-
comment: Some("Created by DDRS".to_string()),
738+
comment: "Created by DDRS".to_string(),
680739
}],
681740
api_url: mock.uri(),
682741
};

0 commit comments

Comments
 (0)