more command options and api fixes
All checks were successful
Build legacy Nix package on Ubuntu / build (push) Successful in 10m21s
All checks were successful
Build legacy Nix package on Ubuntu / build (push) Successful in 10m21s
This commit is contained in:
parent
4e6248cd0d
commit
824218ad73
5 changed files with 323 additions and 123 deletions
|
@ -7,6 +7,8 @@ jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
- name: install sudo
|
||||||
|
run: apt update && apt install sudo
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- uses: https://github.com/cachix/install-nix-action@v26
|
- uses: https://github.com/cachix/install-nix-action@v26
|
||||||
- name: Building package
|
- name: Building package
|
||||||
|
|
136
src/client.rs
136
src/client.rs
|
@ -1,13 +1,13 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
use reqwest::{
|
use reqwest::{
|
||||||
header::{HeaderMap, HeaderValue},
|
|
||||||
Client, Method, Request, Url,
|
Client, Method, Request, Url,
|
||||||
|
header::{HeaderMap, HeaderValue},
|
||||||
};
|
};
|
||||||
use serde::{de::Visitor, Serializer};
|
use serde::{Serializer, de::Visitor};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::{borrow::Borrow, collections::HashMap};
|
use std::{borrow::Borrow, collections::HashMap};
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub enum RecordType {
|
pub enum RecordType {
|
||||||
A,
|
A,
|
||||||
AAAA,
|
AAAA,
|
||||||
|
@ -24,52 +24,56 @@ pub enum RecordType {
|
||||||
DS,
|
DS,
|
||||||
CAA,
|
CAA,
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
impl Serialize for RecordType {
|
impl ToString for RecordType {
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
fn to_string(&self) -> String {
|
||||||
where
|
match self {
|
||||||
S: Serializer,
|
RecordType::A => "A".to_string(),
|
||||||
{
|
RecordType::AAAA => "AAAA".to_string(),
|
||||||
serializer.serialize_str(match self {
|
RecordType::NS => "NS".to_string(),
|
||||||
RecordType::A => "A",
|
RecordType::MX => "MX".to_string(),
|
||||||
RecordType::AAAA => "AAAA",
|
RecordType::CNAME => "CNAME".to_string(),
|
||||||
RecordType::NS => "NS",
|
RecordType::RP => "RP".to_string(),
|
||||||
RecordType::MX => "MX",
|
RecordType::TXT => "TXT".to_string(),
|
||||||
RecordType::CNAME => "CNAME",
|
RecordType::SOA => "SOA".to_string(),
|
||||||
RecordType::RP => "RP",
|
RecordType::HINFO => "HINFO".to_string(),
|
||||||
RecordType::TXT => "TXT",
|
RecordType::SRV => "SRV".to_string(),
|
||||||
RecordType::SOA => "SOA",
|
RecordType::DANE => "DANE".to_string(),
|
||||||
RecordType::HINFO => "HINFO",
|
RecordType::TLSA => "TLSA".to_string(),
|
||||||
RecordType::SRV => "SRV",
|
RecordType::DS => "DS".to_string(),
|
||||||
RecordType::DANE => "DANE",
|
RecordType::CAA => "CAA".to_string(),
|
||||||
RecordType::TLSA => "TLSA",
|
}
|
||||||
RecordType::DS => "DS",
|
}
|
||||||
RecordType::CAA => "CAA",
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&str> for RecordType {
|
||||||
|
type Error = &'static str;
|
||||||
|
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||||
|
Ok(match value {
|
||||||
|
"A" => RecordType::A,
|
||||||
|
"AAAA" => RecordType::AAAA,
|
||||||
|
"NS" => RecordType::NS,
|
||||||
|
"MX" => RecordType::MX,
|
||||||
|
"CNAME" => RecordType::CNAME,
|
||||||
|
"RP" => RecordType::RP,
|
||||||
|
"TXT" => RecordType::TXT,
|
||||||
|
"SOA" => RecordType::SOA,
|
||||||
|
"HINFO" => RecordType::HINFO,
|
||||||
|
"SRV" => RecordType::SRV,
|
||||||
|
"DANE" => RecordType::DANE,
|
||||||
|
"TLSA" => RecordType::TLSA,
|
||||||
|
"DS" => RecordType::DS,
|
||||||
|
"CAA" => RecordType::CAA,
|
||||||
|
_ => return Err(""),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'de> Visitor<'de> for RecordType {
|
#[derive(Serialize)]
|
||||||
type Value = &'static str;
|
struct _RecordQuery {
|
||||||
|
records: Vec<RecordPayload>,
|
||||||
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
||||||
write!(formatter, "a dns record string")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'de> Deserialize<'de> for RecordType {
|
|
||||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
||||||
where
|
|
||||||
D: serde::Deserializer<'de> {
|
|
||||||
deserializer.deserialize_str(RecordType)
|
|
||||||
}
|
|
||||||
fn deserialize_in_place<D>(deserializer: D, place: &mut Self) -> Result<(), D::Error>
|
|
||||||
where
|
|
||||||
D: serde::Deserializer<'de>, {
|
|
||||||
deserializer.deserialize_str()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
pub struct HetznerDNSAPIClient {
|
pub struct HetznerDNSAPIClient {
|
||||||
token: String,
|
token: String,
|
||||||
host: Url,
|
host: Url,
|
||||||
|
@ -112,9 +116,18 @@ impl HetznerDNSAPIClient {
|
||||||
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).map_err(|e| { println!("body encoding error: {}",e); ()} )?.into());
|
*req.body_mut() = Some(
|
||||||
|
serde_json::to_string(&payload)
|
||||||
|
.map_err(|e| {
|
||||||
|
println!("body encoding error: {}", e);
|
||||||
|
()
|
||||||
|
})?
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
println!("{:#?}", serde_json::to_string(&payload));
|
||||||
}
|
}
|
||||||
let t = self.client
|
let t = self
|
||||||
|
.client
|
||||||
.execute(req)
|
.execute(req)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
|
@ -122,10 +135,20 @@ impl HetznerDNSAPIClient {
|
||||||
()
|
()
|
||||||
})?
|
})?
|
||||||
.error_for_status()
|
.error_for_status()
|
||||||
.map_err(|e| { println!("request error: {}", e); ()})?
|
.map_err(|e| {
|
||||||
.text().await
|
println!("request error: {}", e);
|
||||||
.map_err(|e| { println!("request decoding error: {}", e); ()})?;
|
()
|
||||||
Ok(serde_json::from_str::<T>(&t).map_err(|e| {println!("json response parsing 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);
|
||||||
|
()
|
||||||
|
})?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_zones<'a>(
|
pub async fn get_zones<'a>(
|
||||||
|
@ -134,8 +157,9 @@ impl HetznerDNSAPIClient {
|
||||||
page: Option<u32>,
|
page: Option<u32>,
|
||||||
per_page: Option<u32>,
|
per_page: Option<u32>,
|
||||||
search_name: Option<&'a str>,
|
search_name: Option<&'a str>,
|
||||||
) -> Result<ZoneResult, ()> {
|
) -> Result<Vec<Zone>, ()> {
|
||||||
self.api_call(
|
let result: ZoneResult = self
|
||||||
|
.api_call(
|
||||||
"zones",
|
"zones",
|
||||||
Method::GET,
|
Method::GET,
|
||||||
Some(&[
|
Some(&[
|
||||||
|
@ -146,7 +170,8 @@ impl HetznerDNSAPIClient {
|
||||||
]),
|
]),
|
||||||
None::<String>,
|
None::<String>,
|
||||||
)
|
)
|
||||||
.await
|
.await?;
|
||||||
|
Ok(result.zones)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub 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, ()> {
|
||||||
|
@ -285,14 +310,17 @@ impl HetznerDNSAPIClient {
|
||||||
"records/bulk",
|
"records/bulk",
|
||||||
Method::POST,
|
Method::POST,
|
||||||
None::<[(&str, &str); 0]>,
|
None::<[(&str, &str); 0]>,
|
||||||
Some(payloads),
|
Some(_RecordQuery { records: payloads }),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| ())?;
|
.map_err(|_| ())?;
|
||||||
Ok(result.records)
|
Ok(result.records)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn update_records(&self, payloads: Vec<RecordPayload>) -> Result<Vec<Record>, ()> {
|
pub async fn update_records(
|
||||||
|
&self,
|
||||||
|
payloads: Vec<(String, RecordPayload)>,
|
||||||
|
) -> Result<Vec<Record>, ()> {
|
||||||
let result: RecordsResult = self
|
let result: RecordsResult = self
|
||||||
.api_call(
|
.api_call(
|
||||||
"records/bulk",
|
"records/bulk",
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
#![allow(dead_code, unused)]
|
#![allow(dead_code, unused)]
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use crate::models::*;
|
use crate::models::*;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
|
|
||||||
pub mod models;
|
|
||||||
pub mod client;
|
pub mod client;
|
||||||
|
pub mod models;
|
||||||
|
|
216
src/main.rs
216
src/main.rs
|
@ -1,12 +1,12 @@
|
||||||
#![allow(dead_code, unused)]
|
#![allow(dead_code, unused)]
|
||||||
use core::panic;
|
|
||||||
use std::ops::Sub;
|
|
||||||
use crate::client::*;
|
use crate::client::*;
|
||||||
use crate::models::*;
|
use crate::models::*;
|
||||||
use serde::{Serialize, Deserialize};
|
use core::panic;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::ops::Sub;
|
||||||
|
|
||||||
mod models;
|
|
||||||
mod client;
|
mod client;
|
||||||
|
mod models;
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Debug)]
|
#[derive(PartialEq, Eq, Debug)]
|
||||||
enum Mode {
|
enum Mode {
|
||||||
|
@ -35,27 +35,18 @@ struct ZoneContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct RecordProto<'a> {
|
struct RecordContext {
|
||||||
r#type: RecordType,
|
|
||||||
name: &'a str,
|
|
||||||
value: &'a str,
|
|
||||||
ttl: Option<u64>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct RecordContext<'a> {
|
|
||||||
all: bool,
|
all: bool,
|
||||||
zone: &'a str,
|
records: Vec<RecordPayload>,
|
||||||
records: Vec<RecordProto<'a>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Context<'a> {
|
struct Context {
|
||||||
mode: Mode,
|
mode: Mode,
|
||||||
submode: SubMode,
|
submode: SubMode,
|
||||||
token: Option<String>,
|
token: Option<String>,
|
||||||
zone_context: ZoneContext,
|
zone_context: ZoneContext,
|
||||||
record_context: RecordContext<'a>,
|
record_context: RecordContext,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main(flavor = "current_thread")]
|
#[tokio::main(flavor = "current_thread")]
|
||||||
|
@ -72,8 +63,13 @@ async fn main() {
|
||||||
},
|
},
|
||||||
record_context: RecordContext {
|
record_context: RecordContext {
|
||||||
all: false,
|
all: false,
|
||||||
zone: "",
|
records: vec![RecordPayload {
|
||||||
records: vec![],
|
zone_id: String::new(),
|
||||||
|
r#type: RecordType::A,
|
||||||
|
name: String::new(),
|
||||||
|
value: String::new(),
|
||||||
|
ttl: 0,
|
||||||
|
}],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
let mut _continue = false;
|
let mut _continue = false;
|
||||||
|
@ -135,7 +131,32 @@ async fn main() {
|
||||||
if arg.starts_with("-") {
|
if arg.starts_with("-") {
|
||||||
match arg.as_str() {
|
match arg.as_str() {
|
||||||
"--all" => ctx.record_context.all = true,
|
"--all" => ctx.record_context.all = true,
|
||||||
_ => todo!()
|
"--zone" => {
|
||||||
|
ctx.record_context.records[0].zone_id =
|
||||||
|
std::env::args().nth(idx + 1).unwrap()
|
||||||
|
}
|
||||||
|
"--name" => {
|
||||||
|
ctx.record_context.records[0].name =
|
||||||
|
std::env::args().nth(idx + 1).unwrap()
|
||||||
|
}
|
||||||
|
"--value" => {
|
||||||
|
ctx.record_context.records[0].value =
|
||||||
|
std::env::args().nth(idx + 1).unwrap()
|
||||||
|
}
|
||||||
|
"--type" => {
|
||||||
|
ctx.record_context.records[0].r#type = RecordType::try_from(
|
||||||
|
std::env::args().nth(idx + 1).unwrap().as_str(),
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
"--ttl" => {
|
||||||
|
ctx.record_context.records[0].ttl = std::env::args()
|
||||||
|
.nth(idx + 1)
|
||||||
|
.unwrap()
|
||||||
|
.parse()
|
||||||
|
.unwrap_or(86400)
|
||||||
|
}
|
||||||
|
_ => todo!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -160,6 +181,68 @@ async fn main() {
|
||||||
"{:#?}",
|
"{:#?}",
|
||||||
client.get_zones(None, None, None, None).await.unwrap()
|
client.get_zones(None, None, None, None).await.unwrap()
|
||||||
);
|
);
|
||||||
|
} else if !ctx.zone_context.zone.is_empty() {
|
||||||
|
for name in ctx.zone_context.zone.split(",") {
|
||||||
|
if let Ok(zones) =
|
||||||
|
client.get_zones(Some(name), None, None, None).await
|
||||||
|
{
|
||||||
|
zones.into_iter().for_each(|z| println!("{:#?}", z));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SubMode::Create => {
|
||||||
|
if !ctx.zone_context.name.is_empty() {
|
||||||
|
if let Ok(zone) = client
|
||||||
|
.create_zone(ctx.zone_context.name, Some(ctx.zone_context.ttl))
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
println!("{:#?}", zone);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !ctx.zone_context.zone.is_empty() {
|
||||||
|
eprintln!(
|
||||||
|
"Ignoring additional --zone value - use update/u to update an existing zone"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SubMode::Update => {
|
||||||
|
if !ctx.zone_context.zone.is_empty() && !ctx.zone_context.name.is_empty() {
|
||||||
|
if let Ok(zones) = client
|
||||||
|
.get_zones(Some(ctx.zone_context.zone.as_str()), None, None, None)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
if let Ok(zone) = client
|
||||||
|
.update_zone(
|
||||||
|
zones.into_iter().next().unwrap().id,
|
||||||
|
ctx.zone_context.name,
|
||||||
|
Some(ctx.zone_context.ttl),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
println!("{:#?}", zone);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
eprintln!("Unable to fetch zone {}", ctx.zone_context.zone);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
eprintln!("--zone and --name are required for updating!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SubMode::Delete => {
|
||||||
|
if !ctx.zone_context.zone.is_empty() {
|
||||||
|
if let Ok(zones) = client
|
||||||
|
.get_zones(Some(ctx.zone_context.zone.as_str()), None, None, None)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
if client
|
||||||
|
.delete_zone(zones.into_iter().next().unwrap().id)
|
||||||
|
.await
|
||||||
|
.is_ok()
|
||||||
|
{
|
||||||
|
eprintln!("Successfully deleted {}", ctx.zone_context.zone);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
|
@ -170,10 +253,101 @@ async fn main() {
|
||||||
SubMode::Get => {
|
SubMode::Get => {
|
||||||
if ctx.record_context.all {
|
if ctx.record_context.all {
|
||||||
println!("{:#?}", client.get_records(None, None, None).await.unwrap());
|
println!("{:#?}", client.get_records(None, None, None).await.unwrap());
|
||||||
|
} else if !ctx.record_context.records.is_empty() {
|
||||||
|
for zone in ctx
|
||||||
|
.record_context
|
||||||
|
.records
|
||||||
|
.into_iter()
|
||||||
|
.map(|r| r.zone_id)
|
||||||
|
.filter(|z| !z.is_empty())
|
||||||
|
{
|
||||||
|
let zone = client
|
||||||
|
.get_zones(Some(zone.as_str()), None, None, None)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.into_iter()
|
||||||
|
.next()
|
||||||
|
.unwrap();
|
||||||
|
let mut records =
|
||||||
|
client.get_records(None, None, Some(zone.id)).await.unwrap();
|
||||||
|
println!("{:#?}", records);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => { todo!() }
|
|
||||||
}
|
}
|
||||||
|
SubMode::Create => {
|
||||||
|
if !ctx.record_context.records.is_empty() {
|
||||||
|
let zone = &client
|
||||||
|
.get_zones(
|
||||||
|
Some(ctx.record_context.records[0].zone_id.as_str()),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap()[0]
|
||||||
|
.id;
|
||||||
|
ctx.record_context.records[0].zone_id = zone.to_string();
|
||||||
|
println!("{:#?}", ctx.record_context.records);
|
||||||
|
if let Ok(record) =
|
||||||
|
client.create_records(ctx.record_context.records).await
|
||||||
|
{
|
||||||
|
println!("{:#?}", record);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SubMode::Update => {
|
||||||
|
if !ctx.record_context.records.is_empty() {
|
||||||
|
let mut records = vec![];
|
||||||
|
let mut updated_records = vec![];
|
||||||
|
let mut records_iter = ctx.record_context.records.into_iter();
|
||||||
|
for zone in records_iter
|
||||||
|
.clone()
|
||||||
|
.map(|r| r.zone_id)
|
||||||
|
.filter(|z| !z.is_empty())
|
||||||
|
{
|
||||||
|
let zone = client
|
||||||
|
.get_zones(Some(zone.as_str()), None, None, None)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.into_iter()
|
||||||
|
.next()
|
||||||
|
.unwrap();
|
||||||
|
records.extend(
|
||||||
|
client.get_records(None, None, Some(zone.id)).await.unwrap(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
for old_record in records {
|
||||||
|
if let Some(mut new_record) =
|
||||||
|
records_iter.find(|r| r.name == old_record.name)
|
||||||
|
{
|
||||||
|
let mut old_record = old_record;
|
||||||
|
updated_records.push((
|
||||||
|
old_record.id,
|
||||||
|
RecordPayload {
|
||||||
|
zone_id: old_record.zone_id,
|
||||||
|
r#type: old_record.r#type,
|
||||||
|
name: old_record.name,
|
||||||
|
value: new_record.value,
|
||||||
|
ttl: new_record.ttl,
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if updated_records.len() > 0 {
|
||||||
|
let records = client.update_records(updated_records).await.unwrap();
|
||||||
|
eprintln!("Updated {} records", records.len());
|
||||||
|
} else {
|
||||||
|
eprintln!(
|
||||||
|
"No records found that require updating. Did you mean to create/c the records instead?"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SubMode::Delete => {}
|
||||||
|
_ => {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
},
|
||||||
Mode::PrimaryServer => {
|
Mode::PrimaryServer => {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
use serde::{Serialize, Deserialize};
|
|
||||||
use chrono::{DateTime, Utc};
|
|
||||||
use crate::client::RecordType;
|
use crate::client::RecordType;
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::borrow::Borrow;
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct TxtVerification {
|
pub struct TxtVerification {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub token: String
|
pub token: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
|
@ -13,12 +14,12 @@ pub struct Pagination {
|
||||||
pub page: u32,
|
pub page: u32,
|
||||||
pub per_page: u32,
|
pub per_page: u32,
|
||||||
pub last_page: u32,
|
pub last_page: u32,
|
||||||
pub total_entries: u32
|
pub total_entries: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct Meta {
|
pub struct Meta {
|
||||||
pub pagination: Pagination
|
pub pagination: Pagination,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
|
@ -42,59 +43,56 @@ pub struct Zone {
|
||||||
//pub verified: Option<DateTime<Utc>>,
|
//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: TxtVerification
|
pub txt_verification: TxtVerification,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct ZoneResult {
|
pub struct ZoneResult {
|
||||||
pub zones: Vec<Zone>,
|
pub zones: Vec<Zone>,
|
||||||
pub meta: Meta
|
pub meta: Meta,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct RecordPayload {
|
pub struct RecordPayload {
|
||||||
zone_id: String,
|
pub zone_id: String,
|
||||||
r#type: RecordType,
|
pub r#type: RecordType,
|
||||||
name: String,
|
pub name: String,
|
||||||
value: String,
|
pub value: String,
|
||||||
ttl: u64
|
pub ttl: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct Record {
|
pub struct Record {
|
||||||
id: String,
|
pub id: String,
|
||||||
#[serde(with = "hetzner_date")]
|
#[serde(with = "hetzner_date")]
|
||||||
created: DateTime<Utc>,
|
pub created: DateTime<Utc>,
|
||||||
#[serde(with = "hetzner_date")]
|
#[serde(with = "hetzner_date")]
|
||||||
modified: DateTime<Utc>,
|
pub modified: DateTime<Utc>,
|
||||||
zone_id: String,
|
pub zone_id: String,
|
||||||
r#type: String,
|
pub r#type: RecordType,
|
||||||
name: String,
|
pub name: String,
|
||||||
value: String,
|
pub value: String,
|
||||||
ttl: Option<u64>
|
pub ttl: Option<u64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct RecordResult {
|
pub struct RecordResult {
|
||||||
pub record: Record
|
pub record: Record,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct RecordsResult {
|
pub struct RecordsResult {
|
||||||
pub records: Vec<Record>
|
pub records: Vec<Record>,
|
||||||
}
|
}
|
||||||
|
|
||||||
mod hetzner_date {
|
mod hetzner_date {
|
||||||
use chrono::{DateTime, Utc, NaiveDateTime};
|
use chrono::{DateTime, NaiveDateTime, Utc};
|
||||||
use serde::{self, Deserialize, Serializer, Deserializer};
|
use serde::{self, Deserialize, Deserializer, Serializer};
|
||||||
|
|
||||||
// 2025-01-06 02:18:34.674 +0000 UTC
|
// 2025-01-06 02:18:34.674 +0000 UTC
|
||||||
const FORMAT: &str = "%F %T.%-f";
|
const FORMAT: &str = "%F %T.%-f";
|
||||||
|
|
||||||
pub fn serialize<S>(
|
pub fn serialize<S>(date: &DateTime<Utc>, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
date: &DateTime<Utc>,
|
|
||||||
serializer: S,
|
|
||||||
) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
where
|
||||||
S: Serializer,
|
S: Serializer,
|
||||||
{
|
{
|
||||||
|
@ -102,14 +100,13 @@ mod hetzner_date {
|
||||||
serializer.serialize_str(&s)
|
serializer.serialize_str(&s)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deserialize<'de, D>(
|
pub fn deserialize<'de, D>(deserializer: D) -> Result<DateTime<Utc>, D::Error>
|
||||||
deserializer: D,
|
|
||||||
) -> Result<DateTime<Utc>, D::Error>
|
|
||||||
where
|
where
|
||||||
D: Deserializer<'de>,
|
D: Deserializer<'de>,
|
||||||
{
|
{
|
||||||
let s = String::deserialize(deserializer)?;
|
let s = String::deserialize(deserializer)?;
|
||||||
let dt = NaiveDateTime::parse_from_str(&s.split(" +").next().unwrap(), FORMAT).map_err(serde::de::Error::custom)?;
|
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))
|
Ok(DateTime::<Utc>::from_naive_utc_and_offset(dt, Utc))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue