add command and various bug fixes (tests needed aaaa)
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Failing after 1m18s
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Failing after 1m18s
This commit is contained in:
parent
e1835d0f2c
commit
9e42f2e53d
6 changed files with 297 additions and 52 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -295,7 +295,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
|
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hetzner_ddns"
|
name = "hetzner_dns"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
[package]
|
[package]
|
||||||
name = "hetzner_ddns"
|
name = "hetzner_dns"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
|
|
100
src/client.rs
100
src/client.rs
|
@ -72,7 +72,7 @@ impl<'de> Deserialize<'de> for RecordType {
|
||||||
*/
|
*/
|
||||||
pub struct HetznerDNSAPIClient {
|
pub struct HetznerDNSAPIClient {
|
||||||
token: String,
|
token: String,
|
||||||
host: &'static str,
|
host: Url,
|
||||||
client: Client,
|
client: Client,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,12 +80,12 @@ impl HetznerDNSAPIClient {
|
||||||
pub fn new(token: String) -> Self {
|
pub fn new(token: String) -> Self {
|
||||||
HetznerDNSAPIClient {
|
HetznerDNSAPIClient {
|
||||||
token,
|
token,
|
||||||
host: "https://dns.hetzner.com/api/v1",
|
host: Url::parse("https://dns.hetzner.com/api/v1/").unwrap(),
|
||||||
client: Client::new(),
|
client: Client::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn api_call<'a, T, U, I, K, V>(
|
pub async fn api_call<'a, T, U, I, K, V>(
|
||||||
&self,
|
&self,
|
||||||
url: &'a str,
|
url: &'a str,
|
||||||
method: Method,
|
method: Method,
|
||||||
|
@ -100,24 +100,35 @@ impl HetznerDNSAPIClient {
|
||||||
K: AsRef<str>,
|
K: AsRef<str>,
|
||||||
V: AsRef<str>,
|
V: AsRef<str>,
|
||||||
{
|
{
|
||||||
let mut req = Request::new(method, Url::parse(&url).map_err(|_| ())?);
|
let mut req = Request::new(
|
||||||
|
method,
|
||||||
|
self.host.join(url).map_err(|e| {
|
||||||
|
println!("url formatting error: {}", e);
|
||||||
|
()
|
||||||
|
})?,
|
||||||
|
);
|
||||||
req.headers_mut().append(
|
req.headers_mut().append(
|
||||||
"Auth-API-Key",
|
"Auth-API-Token",
|
||||||
HeaderValue::from_str(self.token.as_str()).unwrap(),
|
HeaderValue::from_str(self.token.as_str()).unwrap(),
|
||||||
);
|
);
|
||||||
if let Some(payload) = payload {
|
if let Some(payload) = payload {
|
||||||
*req.body_mut() = Some(serde_json::to_string(&payload).unwrap().into());
|
*req.body_mut() = Some(serde_json::to_string(&payload).map_err(|e| { println!("body encoding error: {}",e); ()} )?.into());
|
||||||
}
|
}
|
||||||
Ok(Client::new()
|
let t = self.client
|
||||||
.execute(req)
|
.execute(req)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| ())?
|
.map_err(|e| {
|
||||||
.json::<T>()
|
println!("request execution error: {}", e);
|
||||||
.await
|
()
|
||||||
.map_err(|_| ())?)
|
})?
|
||||||
|
.error_for_status()
|
||||||
|
.map_err(|e| { println!("request error: {}", e); ()})?
|
||||||
|
.text().await
|
||||||
|
.map_err(|e| { println!("request decoding error: {}", e); ()})?;
|
||||||
|
Ok(serde_json::from_str::<T>(&t).map_err(|e| {println!("json response parsing error: {}",e); ()})?)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_zones<'a>(
|
pub async fn get_zones<'a>(
|
||||||
&self,
|
&self,
|
||||||
name: Option<&'a str>,
|
name: Option<&'a str>,
|
||||||
page: Option<u32>,
|
page: Option<u32>,
|
||||||
|
@ -125,7 +136,7 @@ impl HetznerDNSAPIClient {
|
||||||
search_name: Option<&'a str>,
|
search_name: Option<&'a str>,
|
||||||
) -> Result<ZoneResult, ()> {
|
) -> Result<ZoneResult, ()> {
|
||||||
self.api_call(
|
self.api_call(
|
||||||
"/zones",
|
"zones",
|
||||||
Method::GET,
|
Method::GET,
|
||||||
Some(&[
|
Some(&[
|
||||||
("name", name.unwrap_or_default()),
|
("name", name.unwrap_or_default()),
|
||||||
|
@ -138,19 +149,19 @@ impl HetznerDNSAPIClient {
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_zone(&self, name: String, ttl: Option<u64>) -> Result<Zone, ()> {
|
pub async fn create_zone(&self, name: String, ttl: Option<u64>) -> Result<Zone, ()> {
|
||||||
self.api_call(
|
self.api_call(
|
||||||
"/zones",
|
"zones",
|
||||||
Method::POST,
|
Method::POST,
|
||||||
None::<&[(&str, &str); 0]>,
|
None::<&[(&str, &str); 0]>,
|
||||||
Some(&[("name", name), ("ttl", ttl.unwrap_or(u64::MAX).to_string())]),
|
Some(&[("name", name), ("ttl", ttl.unwrap_or(86400).to_string())]),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_zone(&self, id: String) -> Result<Zone, ()> {
|
pub async fn get_zone(&self, id: String) -> Result<Zone, ()> {
|
||||||
self.api_call(
|
self.api_call(
|
||||||
format!("/zones/{}", id).as_str(),
|
format!("zones/{}", id).as_str(),
|
||||||
Method::GET,
|
Method::GET,
|
||||||
None::<[(&str, &str); 0]>,
|
None::<[(&str, &str); 0]>,
|
||||||
None::<&str>,
|
None::<&str>,
|
||||||
|
@ -158,19 +169,24 @@ impl HetznerDNSAPIClient {
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn update_zone(&self, id: String, name: String, ttl: Option<u64>) -> Result<Zone, ()> {
|
pub async fn update_zone(
|
||||||
|
&self,
|
||||||
|
id: String,
|
||||||
|
name: String,
|
||||||
|
ttl: Option<u64>,
|
||||||
|
) -> Result<Zone, ()> {
|
||||||
self.api_call(
|
self.api_call(
|
||||||
format!("/zones/{}", id).as_str(),
|
format!("zones/{}", id).as_str(),
|
||||||
Method::PUT,
|
Method::PUT,
|
||||||
None::<[(&str, &str); 0]>,
|
None::<[(&str, &str); 0]>,
|
||||||
Some(&[("name", name), ("ttl", ttl.unwrap_or(u64::MAX).to_string())]),
|
Some(&[("name", name), ("ttl", ttl.unwrap_or(86400).to_string())]),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn delete_zone(&self, id: String) -> Result<(), ()> {
|
pub async fn delete_zone(&self, id: String) -> Result<(), ()> {
|
||||||
self.api_call(
|
self.api_call(
|
||||||
format!("/zones/{}", id).as_str(),
|
format!("zones/{}", id).as_str(),
|
||||||
Method::DELETE,
|
Method::DELETE,
|
||||||
None::<&[(&str, &str); 0]>,
|
None::<&[(&str, &str); 0]>,
|
||||||
None::<&str>,
|
None::<&str>,
|
||||||
|
@ -178,17 +194,17 @@ impl HetznerDNSAPIClient {
|
||||||
.await?
|
.await?
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn import_zone() {
|
pub async fn import_zone() {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
async fn export_zone() {
|
pub async fn export_zone() {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
async fn validate_zone() {
|
pub async fn validate_zone() {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_records(
|
pub async fn get_records(
|
||||||
&self,
|
&self,
|
||||||
page: Option<u32>,
|
page: Option<u32>,
|
||||||
per_page: Option<u32>,
|
per_page: Option<u32>,
|
||||||
|
@ -196,7 +212,7 @@ impl HetznerDNSAPIClient {
|
||||||
) -> Result<Vec<Record>, ()> {
|
) -> Result<Vec<Record>, ()> {
|
||||||
let result: RecordsResult = self
|
let result: RecordsResult = self
|
||||||
.api_call(
|
.api_call(
|
||||||
"/records",
|
"records",
|
||||||
Method::GET,
|
Method::GET,
|
||||||
Some(&[
|
Some(&[
|
||||||
("page", page.unwrap_or(1).to_string()),
|
("page", page.unwrap_or(1).to_string()),
|
||||||
|
@ -210,10 +226,10 @@ impl HetznerDNSAPIClient {
|
||||||
Ok(result.records)
|
Ok(result.records)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_record(&self, payload: RecordPayload) -> Result<Record, ()> {
|
pub async fn create_record(&self, payload: RecordPayload) -> Result<Record, ()> {
|
||||||
let result: RecordResult = self
|
let result: RecordResult = self
|
||||||
.api_call(
|
.api_call(
|
||||||
"/records",
|
"records",
|
||||||
Method::POST,
|
Method::POST,
|
||||||
None::<[(&str, &str); 0]>,
|
None::<[(&str, &str); 0]>,
|
||||||
Some(payload),
|
Some(payload),
|
||||||
|
@ -223,10 +239,10 @@ impl HetznerDNSAPIClient {
|
||||||
Ok(result.record)
|
Ok(result.record)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_record(&self, record_id: String) -> Result<Record, ()> {
|
pub async fn get_record(&self, record_id: String) -> Result<Record, ()> {
|
||||||
let result: RecordResult = self
|
let result: RecordResult = self
|
||||||
.api_call(
|
.api_call(
|
||||||
format!("/records/{}", record_id).as_str(),
|
format!("records/{}", record_id).as_str(),
|
||||||
Method::GET,
|
Method::GET,
|
||||||
None::<[(&str, &str); 0]>,
|
None::<[(&str, &str); 0]>,
|
||||||
None::<u8>,
|
None::<u8>,
|
||||||
|
@ -236,10 +252,14 @@ impl HetznerDNSAPIClient {
|
||||||
Ok(result.record)
|
Ok(result.record)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn update_record(&self, record_id: String, payload: RecordPayload) -> Result<Record, ()> {
|
pub async fn update_record(
|
||||||
|
&self,
|
||||||
|
record_id: String,
|
||||||
|
payload: RecordPayload,
|
||||||
|
) -> Result<Record, ()> {
|
||||||
let result: RecordResult = self
|
let result: RecordResult = self
|
||||||
.api_call(
|
.api_call(
|
||||||
format!("/records/{}", record_id).as_str(),
|
format!("records/{}", record_id).as_str(),
|
||||||
Method::PUT,
|
Method::PUT,
|
||||||
None::<[(&str, &str); 0]>,
|
None::<[(&str, &str); 0]>,
|
||||||
Some(payload),
|
Some(payload),
|
||||||
|
@ -249,9 +269,9 @@ impl HetznerDNSAPIClient {
|
||||||
Ok(result.record)
|
Ok(result.record)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn delete_record(&self, record_id: String) -> Result<(), ()> {
|
pub async fn delete_record(&self, record_id: String) -> Result<(), ()> {
|
||||||
self.api_call(
|
self.api_call(
|
||||||
format!("/records/{}", record_id).as_str(),
|
format!("records/{}", record_id).as_str(),
|
||||||
Method::DELETE,
|
Method::DELETE,
|
||||||
None::<[(&str, &str); 0]>,
|
None::<[(&str, &str); 0]>,
|
||||||
None::<u8>,
|
None::<u8>,
|
||||||
|
@ -259,10 +279,10 @@ impl HetznerDNSAPIClient {
|
||||||
.await?
|
.await?
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_records(&self, payloads: Vec<RecordPayload>) -> Result<Vec<Record>, ()> {
|
pub async fn create_records(&self, payloads: Vec<RecordPayload>) -> Result<Vec<Record>, ()> {
|
||||||
let result: RecordsResult = self
|
let result: RecordsResult = self
|
||||||
.api_call(
|
.api_call(
|
||||||
"/records/bulk",
|
"records/bulk",
|
||||||
Method::POST,
|
Method::POST,
|
||||||
None::<[(&str, &str); 0]>,
|
None::<[(&str, &str); 0]>,
|
||||||
Some(payloads),
|
Some(payloads),
|
||||||
|
@ -272,10 +292,10 @@ impl HetznerDNSAPIClient {
|
||||||
Ok(result.records)
|
Ok(result.records)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn update_records(&self, payloads: Vec<RecordPayload>) -> Result<Vec<Record>, ()> {
|
pub async fn update_records(&self, payloads: Vec<RecordPayload>) -> Result<Vec<Record>, ()> {
|
||||||
let result: RecordsResult = self
|
let result: RecordsResult = self
|
||||||
.api_call(
|
.api_call(
|
||||||
"/records/bulk",
|
"records/bulk",
|
||||||
Method::PUT,
|
Method::PUT,
|
||||||
None::<[(&str, &str); 0]>,
|
None::<[(&str, &str); 0]>,
|
||||||
Some(payloads),
|
Some(payloads),
|
||||||
|
|
9
src/lib.rs
Normal file
9
src/lib.rs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
#![allow(dead_code, unused)]
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use crate::models::*;
|
||||||
|
use std::error::Error;
|
||||||
|
|
||||||
|
pub mod models;
|
||||||
|
pub mod client;
|
||||||
|
|
191
src/main.rs
191
src/main.rs
|
@ -1,12 +1,193 @@
|
||||||
#![allow(dead_code, unused)]
|
#![allow(dead_code, unused)]
|
||||||
|
use core::panic;
|
||||||
use serde::{Deserialize, Serialize};
|
use std::ops::Sub;
|
||||||
|
use crate::client::*;
|
||||||
use crate::models::*;
|
use crate::models::*;
|
||||||
use std::error::Error;
|
use serde::{Serialize, Deserialize};
|
||||||
|
|
||||||
mod models;
|
mod models;
|
||||||
mod client;
|
mod client;
|
||||||
|
|
||||||
#[tokio::main(flavor = "current_thread")]
|
#[derive(PartialEq, Eq, Debug)]
|
||||||
async fn main() -> () {
|
enum Mode {
|
||||||
|
Unset,
|
||||||
|
Errornous,
|
||||||
|
Zone,
|
||||||
|
Record,
|
||||||
|
PrimaryServer,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Debug)]
|
||||||
|
enum SubMode {
|
||||||
|
Help,
|
||||||
|
Get,
|
||||||
|
Create,
|
||||||
|
Update,
|
||||||
|
Delete,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct ZoneContext {
|
||||||
|
all: bool,
|
||||||
|
zone: String,
|
||||||
|
name: String,
|
||||||
|
ttl: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct RecordProto<'a> {
|
||||||
|
r#type: RecordType,
|
||||||
|
name: &'a str,
|
||||||
|
value: &'a str,
|
||||||
|
ttl: Option<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct RecordContext<'a> {
|
||||||
|
all: bool,
|
||||||
|
zone: &'a str,
|
||||||
|
records: Vec<RecordProto<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Context<'a> {
|
||||||
|
mode: Mode,
|
||||||
|
submode: SubMode,
|
||||||
|
token: Option<String>,
|
||||||
|
zone_context: ZoneContext,
|
||||||
|
record_context: RecordContext<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main(flavor = "current_thread")]
|
||||||
|
async fn main() {
|
||||||
|
let mut ctx = Context {
|
||||||
|
mode: Mode::Unset,
|
||||||
|
submode: SubMode::Help,
|
||||||
|
token: std::env::var("HETZNER_DNS_API_TOKEN").ok(),
|
||||||
|
zone_context: ZoneContext {
|
||||||
|
all: false,
|
||||||
|
zone: String::new(),
|
||||||
|
name: String::new(),
|
||||||
|
ttl: 86400,
|
||||||
|
},
|
||||||
|
record_context: RecordContext {
|
||||||
|
all: false,
|
||||||
|
zone: "",
|
||||||
|
records: vec![],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let mut _continue = false;
|
||||||
|
for (idx, arg) in std::env::args().enumerate() {
|
||||||
|
if _continue {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
match idx {
|
||||||
|
0 => continue,
|
||||||
|
1 => {
|
||||||
|
ctx.mode = match arg.as_str() {
|
||||||
|
"zones" => Mode::Zone,
|
||||||
|
"records" => Mode::Record,
|
||||||
|
"primary" => Mode::PrimaryServer,
|
||||||
|
"z" => Mode::Zone,
|
||||||
|
"r" => Mode::Record,
|
||||||
|
"p" => Mode::PrimaryServer,
|
||||||
|
_ => Mode::Errornous,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
2 => {
|
||||||
|
ctx.submode = match arg.as_str() {
|
||||||
|
"get" => SubMode::Get,
|
||||||
|
"create" => SubMode::Create,
|
||||||
|
"update" => SubMode::Update,
|
||||||
|
"delete" => SubMode::Delete,
|
||||||
|
"g" => SubMode::Get,
|
||||||
|
"c" => SubMode::Create,
|
||||||
|
"u" => SubMode::Update,
|
||||||
|
"d" => SubMode::Delete,
|
||||||
|
_ => SubMode::Help,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => match ctx.mode {
|
||||||
|
Mode::Zone => {
|
||||||
|
if arg.starts_with("-") {
|
||||||
|
match arg.as_str() {
|
||||||
|
"--all" => ctx.zone_context.all = true,
|
||||||
|
"--name" => {
|
||||||
|
ctx.zone_context.name = std::env::args().nth(idx + 1).unwrap()
|
||||||
|
}
|
||||||
|
"--ttl" => {
|
||||||
|
ctx.zone_context.ttl = std::env::args()
|
||||||
|
.nth(idx + 1)
|
||||||
|
.unwrap()
|
||||||
|
.parse()
|
||||||
|
.unwrap_or(86400)
|
||||||
|
}
|
||||||
|
"--zone" => {
|
||||||
|
ctx.zone_context.zone = std::env::args().nth(idx + 1).unwrap()
|
||||||
|
}
|
||||||
|
_ => panic!("unknown parameter {}", arg),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
continue; // value, ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Mode::Record => {
|
||||||
|
if arg.starts_with("-") {
|
||||||
|
match arg.as_str() {
|
||||||
|
"--all" => ctx.record_context.all = true,
|
||||||
|
_ => todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Mode::PrimaryServer => {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.mode != Mode::Unset && ctx.mode != Mode::Errornous && ctx.submode != SubMode::Help {
|
||||||
|
if let Some(ref token) = ctx.token {
|
||||||
|
let client = HetznerDNSAPIClient::new(String::from(token.trim()));
|
||||||
|
match ctx.mode {
|
||||||
|
Mode::Zone => match ctx.submode {
|
||||||
|
SubMode::Get => {
|
||||||
|
if ctx.zone_context.all {
|
||||||
|
println!(
|
||||||
|
"{:#?}",
|
||||||
|
client.get_zones(None, None, None, None).await.unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Mode::Record => match ctx.submode {
|
||||||
|
SubMode::Get => {
|
||||||
|
if ctx.record_context.all {
|
||||||
|
println!("{:#?}", client.get_records(None, None, None).await.unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => { todo!() }
|
||||||
|
}
|
||||||
|
Mode::PrimaryServer => {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
_ => panic!("how in the even"),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic!("missing token!");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match ctx.mode {
|
||||||
|
Mode::Zone => println!("zone help"),
|
||||||
|
Mode::Record => println!("record help"),
|
||||||
|
Mode::PrimaryServer => println!("primary server help"),
|
||||||
|
_ => println!("full help"),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,10 +24,12 @@ pub struct Meta {
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct Zone {
|
pub struct Zone {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
|
#[serde(with = "hetzner_date")]
|
||||||
pub created: DateTime<Utc>,
|
pub created: DateTime<Utc>,
|
||||||
|
#[serde(with = "hetzner_date")]
|
||||||
pub modified: DateTime<Utc>,
|
pub modified: DateTime<Utc>,
|
||||||
pub legacy_dns_host: String,
|
pub legacy_dns_host: String,
|
||||||
pub legacy_dns: Vec<String>,
|
pub legacy_dns: Option<Vec<String>>,
|
||||||
pub ns: Vec<String>,
|
pub ns: Vec<String>,
|
||||||
pub owner: String,
|
pub owner: String,
|
||||||
pub paused: bool,
|
pub paused: bool,
|
||||||
|
@ -35,11 +37,12 @@ pub struct Zone {
|
||||||
pub project: String,
|
pub project: String,
|
||||||
pub registrar: String,
|
pub registrar: String,
|
||||||
pub status: String,
|
pub status: String,
|
||||||
pub ttl: u32,
|
pub ttl: Option<u64>,
|
||||||
pub verified: DateTime<Utc>,
|
//#[serde(with = "hetzner_date")] // verified strings are empty, so its useless anyway
|
||||||
|
//pub verified: Option<DateTime<Utc>>,
|
||||||
pub records_count: u32,
|
pub records_count: u32,
|
||||||
pub is_secondary_dns: bool,
|
pub is_secondary_dns: bool,
|
||||||
pub txt_verification: Vec<TxtVerification>
|
pub txt_verification: TxtVerification
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
|
@ -60,13 +63,15 @@ pub struct RecordPayload {
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct Record {
|
pub struct Record {
|
||||||
id: String,
|
id: String,
|
||||||
|
#[serde(with = "hetzner_date")]
|
||||||
created: DateTime<Utc>,
|
created: DateTime<Utc>,
|
||||||
|
#[serde(with = "hetzner_date")]
|
||||||
modified: DateTime<Utc>,
|
modified: DateTime<Utc>,
|
||||||
zone_id: String,
|
zone_id: String,
|
||||||
r#type: String,
|
r#type: String,
|
||||||
name: String,
|
name: String,
|
||||||
value: String,
|
value: String,
|
||||||
ttl: u64
|
ttl: Option<u64>
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
@ -78,3 +83,33 @@ pub struct RecordResult {
|
||||||
pub struct RecordsResult {
|
pub struct RecordsResult {
|
||||||
pub records: Vec<Record>
|
pub records: Vec<Record>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mod hetzner_date {
|
||||||
|
use chrono::{DateTime, Utc, NaiveDateTime};
|
||||||
|
use serde::{self, Deserialize, Serializer, Deserializer};
|
||||||
|
|
||||||
|
// 2025-01-06 02:18:34.674 +0000 UTC
|
||||||
|
const FORMAT: &str = "%F %T.%-f";
|
||||||
|
|
||||||
|
pub fn serialize<S>(
|
||||||
|
date: &DateTime<Utc>,
|
||||||
|
serializer: S,
|
||||||
|
) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
let s = format!("{}", date.format(FORMAT));
|
||||||
|
serializer.serialize_str(&s)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize<'de, D>(
|
||||||
|
deserializer: D,
|
||||||
|
) -> Result<DateTime<Utc>, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let s = String::deserialize(deserializer)?;
|
||||||
|
let dt = NaiveDateTime::parse_from_str(&s.split(" +").next().unwrap(), FORMAT).map_err(serde::de::Error::custom)?;
|
||||||
|
Ok(DateTime::<Utc>::from_naive_utc_and_offset(dt, Utc))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue