Merge branch 'release/0.2.1'
This commit is contained in:
commit
2920328093
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,2 +1,3 @@
|
||||||
/target
|
/target
|
||||||
**/*.rs.bk
|
**/*.rs.bk
|
||||||
|
.envrc
|
50
.vscode/launch.json
vendored
Normal file
50
.vscode/launch.json
vendored
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.1",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"type": "lldb",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Debug 'cloudflare-ddns'",
|
||||||
|
"cargo": {
|
||||||
|
"args": [
|
||||||
|
"build",
|
||||||
|
"--bin=cloudflare-ddns",
|
||||||
|
"--package=cloudflare-ddns"
|
||||||
|
],
|
||||||
|
"filter": {
|
||||||
|
"name": "cloudflare-ddns",
|
||||||
|
"kind": "bin"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"args": [
|
||||||
|
"--key=${env:DDNS_AUTH_KEY}",
|
||||||
|
"--domain=${env:DOMAIN}",
|
||||||
|
"--email=${env:EMAIL}",
|
||||||
|
"--zone=${env:ZONE}"
|
||||||
|
],
|
||||||
|
"cwd": "${workspaceFolder}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "lldb",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Debug unit tests in 'cloudflare-ddns'",
|
||||||
|
"cargo": {
|
||||||
|
"args": [
|
||||||
|
"test",
|
||||||
|
"--no-run",
|
||||||
|
"--bin=cloudflare-ddns",
|
||||||
|
"--package=cloudflare-ddns"
|
||||||
|
],
|
||||||
|
"filter": {
|
||||||
|
"name": "cloudflare-ddns",
|
||||||
|
"kind": "bin"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"args": [],
|
||||||
|
"cwd": "${workspaceFolder}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
1592
Cargo.lock
generated
1592
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
18
Cargo.toml
18
Cargo.toml
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "cloudflare-ddns"
|
name = "cloudflare-ddns"
|
||||||
version = "0.2.0"
|
version = "0.2.1"
|
||||||
authors = ["Rostislav Raykov <z@zbrox.org>"]
|
authors = ["Rostislav Raykov <z@zbrox.org>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
description = "A simple CLI tool to use Cloudflare's free DDNS service"
|
description = "A simple CLI tool to use Cloudflare's free DDNS service"
|
||||||
|
@ -14,11 +14,11 @@ readme = "README.md"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
quicli = "0.4"
|
quicli = "0.4"
|
||||||
structopt = "0.3.1"
|
structopt = "0.3.12"
|
||||||
reqwest="0.9.20"
|
reqwest= { version = "0.10.4", features = ["blocking", "json"] }
|
||||||
serde = "1.0.101"
|
serde = "1.0.105"
|
||||||
serde_json = "1.0.40"
|
serde_json = "1.0.48"
|
||||||
exitcode = "1.1.2"
|
toml = "0.5.6"
|
||||||
human-panic = "1.0.1"
|
human-panic = "1.0.2"
|
||||||
failure = "0.1.5"
|
serde_derive = "1.0.105"
|
||||||
toml = "0.5.3"
|
anyhow = "1.0.27"
|
||||||
|
|
18
src/file.rs
18
src/file.rs
|
@ -1,12 +1,18 @@
|
||||||
use failure::Error;
|
use std::fs::File;
|
||||||
use quicli::fs::{write_to_file, read_file};
|
use std::io::prelude::*;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
pub fn read_cache_file(path: &PathBuf) -> Result<String, Error> {
|
pub fn read_file(path: &PathBuf) -> Result<String> {
|
||||||
Ok(read_file(path)?)
|
let mut file = File::open(path)?;
|
||||||
|
let mut contents = String::new();
|
||||||
|
file.read_to_string(&mut contents)?;
|
||||||
|
|
||||||
|
Ok(contents)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_cache_file(path: &PathBuf, ip: &str) -> Result<(), Error> {
|
pub fn write_file(path: &PathBuf, ip: &str) -> Result<()> {
|
||||||
write_to_file(path, ip)?;
|
let mut file = File::create(path)?;
|
||||||
|
file.write_all(ip.as_bytes())?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
20
src/main.rs
20
src/main.rs
|
@ -1,13 +1,13 @@
|
||||||
mod file;
|
mod file;
|
||||||
mod network;
|
mod network;
|
||||||
|
|
||||||
use file::{read_cache_file, write_cache_file};
|
use file::{read_file, write_file};
|
||||||
use human_panic::setup_panic;
|
use human_panic::setup_panic;
|
||||||
use network::{get_current_ip, get_dns_record_id, get_zone_identifier, update_ddns};
|
use network::{get_current_ip, get_dns_record_id, get_zone_identifier, update_ddns};
|
||||||
use quicli::fs::read_file;
|
|
||||||
use quicli::prelude::*;
|
use quicli::prelude::*;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct Config {
|
struct Config {
|
||||||
|
@ -45,7 +45,7 @@ struct Cli {
|
||||||
cache: Option<PathBuf>,
|
cache: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> CliResult {
|
fn main() -> Result<()> {
|
||||||
setup_panic!();
|
setup_panic!();
|
||||||
let args = Cli::from_args();
|
let args = Cli::from_args();
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ fn main() -> CliResult {
|
||||||
let cached_ip: Option<String> = match args.cache.clone() {
|
let cached_ip: Option<String> = match args.cache.clone() {
|
||||||
Some(v) => {
|
Some(v) => {
|
||||||
if v.exists() {
|
if v.exists() {
|
||||||
Some(read_cache_file(&v.clone())?)
|
Some(read_file(&v.clone()).context("Could not read cache file")?)
|
||||||
} else {
|
} else {
|
||||||
Some("0.0.0.0".to_owned())
|
Some("0.0.0.0".to_owned())
|
||||||
}
|
}
|
||||||
|
@ -73,12 +73,12 @@ fn main() -> CliResult {
|
||||||
¤t_ip,
|
¤t_ip,
|
||||||
&args.cache.clone().unwrap()
|
&args.cache.clone().unwrap()
|
||||||
);
|
);
|
||||||
write_cache_file(&args.cache.unwrap(), ¤t_ip)?;
|
write_file(&args.cache.unwrap(), ¤t_ip)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let (email, auth_key, zone, domain) = match args.config {
|
let (email, auth_key, zone, domain) = match args.config {
|
||||||
Some(c) => {
|
Some(c) => {
|
||||||
let config_str = read_file(c)?;
|
let config_str = read_file(&c)?;
|
||||||
let config: Config = toml::from_str(&config_str)?;
|
let config: Config = toml::from_str(&config_str)?;
|
||||||
(config.email, config.auth_key, config.zone, config.domain)
|
(config.email, config.auth_key, config.zone, config.domain)
|
||||||
}
|
}
|
||||||
|
@ -106,9 +106,9 @@ fn update(
|
||||||
auth_key: &str,
|
auth_key: &str,
|
||||||
zone: &str,
|
zone: &str,
|
||||||
domain: &str,
|
domain: &str,
|
||||||
) -> Result<(), Error> {
|
) -> Result<()> {
|
||||||
let zone_id = get_zone_identifier(&zone, &email, &auth_key)?;
|
let zone_id = get_zone_identifier(&zone, &email, &auth_key).context("Error getting the zone identifier")?;
|
||||||
let record_id = get_dns_record_id(&zone_id, &domain, &email, &auth_key)?;
|
let record_id = get_dns_record_id(&zone_id, &domain, &email, &auth_key).context("Error getting the DNS record ID")?;
|
||||||
|
|
||||||
update_ddns(
|
update_ddns(
|
||||||
¤t_ip,
|
¤t_ip,
|
||||||
|
@ -117,7 +117,7 @@ fn update(
|
||||||
&record_id,
|
&record_id,
|
||||||
&email,
|
&email,
|
||||||
&auth_key,
|
&auth_key,
|
||||||
)?;
|
).context("Error updating the DNS record")?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
use failure::{format_err, Error};
|
use serde_derive::{Deserialize, Serialize};
|
||||||
use serde::{Deserialize, Serialize};
|
use anyhow::{anyhow};
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
struct CloudflareListResponse {
|
struct CloudflareListResponse {
|
||||||
success: bool,
|
success: bool,
|
||||||
errors: Vec<String>,
|
errors: Vec<String>,
|
||||||
result: Vec<ObjectWithId>,
|
result: Option<Vec<ObjectWithId>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
|
@ -28,26 +28,41 @@ struct UpdateIpData {
|
||||||
content: String,
|
content: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_zone_identifier(zone: &str, email: &str, key: &str) -> Result<String, Error> {
|
pub fn get_zone_identifier(zone: &str, email: &str, key: &str) -> anyhow::Result<String> {
|
||||||
let client = reqwest::Client::new();
|
let client = reqwest::blocking::Client::new();
|
||||||
let url = format!("https://api.cloudflare.com/client/v4/zones?name={}", zone);
|
let url = format!("https://api.cloudflare.com/client/v4/zones?name={}", zone);
|
||||||
let response: CloudflareListResponse = client
|
|
||||||
|
let response = client
|
||||||
.get(&url)
|
.get(&url)
|
||||||
.header("X-Auth-Email", email)
|
.header("X-Auth-Email", email)
|
||||||
.header("X-Auth-Key", key)
|
.header("X-Auth-Key", key)
|
||||||
.header("Content-Type", "application/json")
|
.header("Content-Type", "application/json")
|
||||||
.send()?
|
.send()?;
|
||||||
|
|
||||||
|
if response.status() != 200 {
|
||||||
|
return Err(anyhow!("API Error: HTTP {}", response.status()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let body: CloudflareListResponse = response
|
||||||
.json()?;
|
.json()?;
|
||||||
if !response.success {
|
|
||||||
let err: String = response
|
if !body.success {
|
||||||
|
let err: String = body
|
||||||
.errors
|
.errors
|
||||||
.iter()
|
.iter()
|
||||||
.map(|s| format!("{}\n", s.to_owned()))
|
.map(|s| format!("{}\n", s.to_owned()))
|
||||||
.collect();
|
.collect();
|
||||||
return Err(format_err!("API Error: {}", err));
|
return Err(anyhow!("API Error: {}", err));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(response.result[0].id.clone())
|
let zones = match body.result {
|
||||||
|
Some(v) => v,
|
||||||
|
None => {
|
||||||
|
return Err(anyhow!("No zones returned"))
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(zones[0].id.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_dns_record_id(
|
pub fn get_dns_record_id(
|
||||||
|
@ -55,32 +70,46 @@ pub fn get_dns_record_id(
|
||||||
domain: &str,
|
domain: &str,
|
||||||
email: &str,
|
email: &str,
|
||||||
key: &str,
|
key: &str,
|
||||||
) -> Result<String, Error> {
|
) -> anyhow::Result<String> {
|
||||||
let client = reqwest::Client::new();
|
let client = reqwest::blocking::Client::new();
|
||||||
let url = format!(
|
let url = format!(
|
||||||
"https://api.cloudflare.com/client/v4/zones/{}/dns_records?name={}",
|
"https://api.cloudflare.com/client/v4/zones/{}/dns_records?name={}",
|
||||||
zone_id, domain
|
zone_id, domain
|
||||||
);
|
);
|
||||||
let response: CloudflareListResponse = client
|
|
||||||
|
let response = client
|
||||||
.get(&url)
|
.get(&url)
|
||||||
.header("X-Auth-Email", email)
|
.header("X-Auth-Email", email)
|
||||||
.header("X-Auth-Key", key)
|
.header("X-Auth-Key", key)
|
||||||
.header("Content-Type", "application/json")
|
.header("Content-Type", "application/json")
|
||||||
.send()?
|
.send()?;
|
||||||
.json()?;
|
|
||||||
if !response.success {
|
if response.status() != 200 {
|
||||||
let err: String = response
|
return Err(anyhow!("API Error: HTTP {}", response.status()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let body: CloudflareListResponse = response.json()?;
|
||||||
|
|
||||||
|
if !body.success {
|
||||||
|
let err: String = body
|
||||||
.errors
|
.errors
|
||||||
.iter()
|
.iter()
|
||||||
.map(|s| format!("{}\n", s.to_owned()))
|
.map(|s| format!("{}\n", s.to_owned()))
|
||||||
.collect();
|
.collect();
|
||||||
return Err(format_err!("API Error: {}", err));
|
return Err(anyhow!("API Error: {}", err));
|
||||||
}
|
}
|
||||||
|
|
||||||
let id = match response.result.first() {
|
let records = match body.result {
|
||||||
|
Some(v) => v,
|
||||||
|
None => {
|
||||||
|
return Err(anyhow!("No DNS records returned"))
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let id = match records.first() {
|
||||||
Some(v) => v.id.clone(),
|
Some(v) => v.id.clone(),
|
||||||
None => {
|
None => {
|
||||||
return Err(format_err!(
|
return Err(anyhow!(
|
||||||
"Unexpected API result for DNS record. Check if you passed the right options."
|
"Unexpected API result for DNS record. Check if you passed the right options."
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
@ -89,8 +118,8 @@ pub fn get_dns_record_id(
|
||||||
Ok(id)
|
Ok(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_current_ip() -> Result<String, Error> {
|
pub fn get_current_ip() -> anyhow::Result<String> {
|
||||||
Ok(reqwest::Client::new()
|
Ok(reqwest::blocking::Client::new()
|
||||||
.get("http://ipv4.icanhazip.com")
|
.get("http://ipv4.icanhazip.com")
|
||||||
.send()?
|
.send()?
|
||||||
.text()?
|
.text()?
|
||||||
|
@ -105,8 +134,8 @@ pub fn update_ddns(
|
||||||
record_id: &str,
|
record_id: &str,
|
||||||
email: &str,
|
email: &str,
|
||||||
key: &str,
|
key: &str,
|
||||||
) -> Result<(), Error> {
|
) -> anyhow::Result<()> {
|
||||||
let client = reqwest::Client::new();
|
let client = reqwest::blocking::Client::new();
|
||||||
let url = format!(
|
let url = format!(
|
||||||
"https://api.cloudflare.com/client/v4/zones/{}/dns_records/{}",
|
"https://api.cloudflare.com/client/v4/zones/{}/dns_records/{}",
|
||||||
zone_id, record_id
|
zone_id, record_id
|
||||||
|
@ -119,22 +148,28 @@ pub fn update_ddns(
|
||||||
content: ip.to_owned(),
|
content: ip.to_owned(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let response: CloudflareUpdateResponse = client
|
let response = client
|
||||||
.put(&url)
|
.put(&url)
|
||||||
.header("X-Auth-Email", email)
|
.header("X-Auth-Email", email)
|
||||||
.header("X-Auth-Key", key)
|
.header("X-Auth-Key", key)
|
||||||
.header("Content-Type", "application/json")
|
.header("Content-Type", "application/json")
|
||||||
.json(&update_data)
|
.json(&update_data)
|
||||||
.send()?
|
.send()?;
|
||||||
|
|
||||||
|
if response.status() != 200 {
|
||||||
|
return Err(anyhow!("API Error: HTTP {}", response.status()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let body: CloudflareUpdateResponse = response
|
||||||
.json()?;
|
.json()?;
|
||||||
|
|
||||||
if !response.success {
|
if !body.success {
|
||||||
let err: String = response
|
let err: String = body
|
||||||
.errors
|
.errors
|
||||||
.iter()
|
.iter()
|
||||||
.map(|s| format!("{}\n", s.to_owned()))
|
.map(|s| format!("{}\n", s.to_owned()))
|
||||||
.collect();
|
.collect();
|
||||||
return Err(format_err!("Unsuccessful update of DNS record: {}", err));
|
return Err(anyhow!("Unsuccessful update of DNS record: {}", err));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
Loading…
Reference in a new issue