add models and api client
Some checks are pending
Build legacy Nix package on Ubuntu / build (push) Waiting to run
Some checks are pending
Build legacy Nix package on Ubuntu / build (push) Waiting to run
This commit is contained in:
parent
da3a2e1dea
commit
7b7bc15241
5 changed files with 317 additions and 3 deletions
1
.envrc
1
.envrc
|
@ -1 +1,2 @@
|
|||
use flake
|
||||
export HETZNER_API_KEY=pBCqf2QRkrLgFglTgEH4PJMQvxSuNOiH
|
||||
|
|
|
@ -8,4 +8,4 @@ chrono = { version = "0.4.40", features = ["serde"] }
|
|||
reqwest = { version = "0.12.15", features = ["json"] }
|
||||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
serde_json = "1.0.140"
|
||||
tokio = { version = "1.44.2", features = ["macros", "rt", "time"] }
|
||||
tokio = { version = "1.44.2", features = ["macros", "time"] }
|
||||
|
|
224
src/client.rs
Normal file
224
src/client.rs
Normal file
|
@ -0,0 +1,224 @@
|
|||
use crate::*;
|
||||
use reqwest::{
|
||||
header::{HeaderMap, HeaderValue},
|
||||
Client, Method, Request, Url,
|
||||
};
|
||||
use serde::{Serializer, de::Visitor};
|
||||
use serde_json::Value;
|
||||
use std::{borrow::Borrow, collections::HashMap};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum RecordType {
|
||||
A,
|
||||
AAAA,
|
||||
NS,
|
||||
MX,
|
||||
CNAME,
|
||||
RP,
|
||||
TXT,
|
||||
SOA,
|
||||
HINFO,
|
||||
SRV,
|
||||
DANE,
|
||||
TLSA,
|
||||
DS,
|
||||
CAA,
|
||||
}
|
||||
/*
|
||||
impl Serialize for RecordType {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_str(match self {
|
||||
RecordType::A => "A",
|
||||
RecordType::AAAA => "AAAA",
|
||||
RecordType::NS => "NS",
|
||||
RecordType::MX => "MX",
|
||||
RecordType::CNAME => "CNAME",
|
||||
RecordType::RP => "RP",
|
||||
RecordType::TXT => "TXT",
|
||||
RecordType::SOA => "SOA",
|
||||
RecordType::HINFO => "HINFO",
|
||||
RecordType::SRV => "SRV",
|
||||
RecordType::DANE => "DANE",
|
||||
RecordType::TLSA => "TLSA",
|
||||
RecordType::DS => "DS",
|
||||
RecordType::CAA => "CAA",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Visitor<'de> for RecordType {
|
||||
type Value = &'static str;
|
||||
|
||||
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 {
|
||||
token: String,
|
||||
host: &'static str,
|
||||
client: Client,
|
||||
}
|
||||
|
||||
impl HetznerDNSAPIClient {
|
||||
pub fn new(token: String) -> Self {
|
||||
HetznerDNSAPIClient {
|
||||
token,
|
||||
host: "https://dns.hetzner.com/api/v1",
|
||||
client: Client::new(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn api_call<'a, T, U, I, K, V>(
|
||||
&self,
|
||||
url: &'a str,
|
||||
method: Method,
|
||||
query: Option<I>,
|
||||
payload: Option<U>,
|
||||
) -> Result<T, ()>
|
||||
where
|
||||
T: for<'de> Deserialize<'de>,
|
||||
U: Serialize,
|
||||
I: IntoIterator,
|
||||
<I as IntoIterator>::Item: Borrow<(K, V)>,
|
||||
K: AsRef<str>,
|
||||
V: AsRef<str>,
|
||||
{
|
||||
let mut req = Request::new(method, Url::parse(&url).map_err(|_| ())?);
|
||||
req.headers_mut().append(
|
||||
"Auth-API-Key",
|
||||
HeaderValue::from_str(self.token.as_str()).unwrap(),
|
||||
);
|
||||
if let Some(payload) = payload {
|
||||
*req.body_mut() = Some(serde_json::to_string(&payload).unwrap().into());
|
||||
}
|
||||
Ok(Client::new()
|
||||
.execute(req)
|
||||
.await
|
||||
.map_err(|_| ())?
|
||||
.json::<T>()
|
||||
.await
|
||||
.map_err(|_| ())?)
|
||||
}
|
||||
|
||||
async fn get_zones<'a>(
|
||||
&self,
|
||||
name: Option<&'a str>,
|
||||
page: Option<u32>,
|
||||
per_page: Option<u32>,
|
||||
search_name: Option<&'a str>,
|
||||
) -> Result<ZoneResult, ()> {
|
||||
self.api_call(
|
||||
"/zones",
|
||||
Method::GET,
|
||||
Some(&[
|
||||
("name", name.unwrap_or_default()),
|
||||
("page", page.unwrap_or(1).to_string().as_str()),
|
||||
("per_page", per_page.unwrap_or(100).to_string().as_str()),
|
||||
("search_name", search_name.unwrap_or_default()),
|
||||
]),
|
||||
None::<String>,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn create_zone(&self, name: String, ttl: Option<u64>) -> Result<Zone, ()> {
|
||||
self.api_call(
|
||||
"/zones",
|
||||
Method::POST,
|
||||
None::<&[(&str, &str); 0]>,
|
||||
Some(&[("name", name), ("ttl", ttl.unwrap_or(u64::MAX).to_string())]),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_zone(&self, id: String) -> Result<Zone, ()> {
|
||||
self.api_call(
|
||||
format!("/zones/{}", id).as_str(),
|
||||
Method::GET,
|
||||
None::<[(&str, &str); 0]>,
|
||||
None::<&str>,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn update_zone(&self, id: String, name: String, ttl: Option<u64>) -> Result<Zone, ()> {
|
||||
self.api_call(
|
||||
format!("/zones/{}", id).as_str(),
|
||||
Method::PUT,
|
||||
None::<[(&str, &str); 0]>,
|
||||
Some(&[("name", name), ("ttl", ttl.unwrap_or(u64::MAX).to_string())]),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn delete_zone(&self, id: String) -> Result<(), ()> {
|
||||
self.api_call(
|
||||
format!("/zones/{}", id).as_str(),
|
||||
Method::DELETE,
|
||||
None::<&[(&str, &str); 0]>,
|
||||
None::<&str>,
|
||||
)
|
||||
.await?
|
||||
}
|
||||
|
||||
async fn import_zone() {
|
||||
todo!()
|
||||
}
|
||||
async fn export_zone() {
|
||||
todo!()
|
||||
}
|
||||
async fn validate_zone() {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn get_records(&self, page: Option<u32>, per_page: Option<u32>, zone_id: Option<String>) -> Result<Vec<Record>, ()> {
|
||||
let result: RecordsResult = self.api_call("/records", Method::GET, Some(&[("page", page.unwrap_or(1).to_string()), ("per_page", per_page.unwrap_or(100).to_string()), ("zone_id", zone_id.unwrap_or_default())]), None::<u8>).await.map_err(|_| ())?;
|
||||
Ok(result.records)
|
||||
}
|
||||
|
||||
async fn create_record(&self, payload: RecordPayload) -> Result<Record, ()> {
|
||||
let result: RecordResult = self.api_call("/records", Method::POST, None::<[(&str, &str); 0]>, Some(payload)).await.map_err(|_| ())?;
|
||||
Ok(result.record)
|
||||
}
|
||||
|
||||
async fn get_record(&self, record_id: String) -> Result<Record, ()> {
|
||||
let result: RecordResult = self.api_call(format!("/records/{}", record_id).as_str(), Method::GET, None::<[(&str, &str); 0]>, None::<u8>).await.map_err(|_| ())?;
|
||||
Ok(result.record)
|
||||
}
|
||||
|
||||
async fn update_record(&self, record_id: String, payload: RecordPayload) -> Result<Record, ()> {
|
||||
let result: RecordResult = self.api_call(format!("/records/{}", record_id).as_str(), Method::PUT, None::<[(&str, &str); 0]>, Some(payload)).await.map_err(|_| ())?;
|
||||
Ok(result.record)
|
||||
}
|
||||
|
||||
async fn delete_record(&self, record_id: String) -> Result<(), ()> {
|
||||
self.api_call(format!("/records/{}", record_id).as_str(), Method::DELETE, None::<[(&str, &str); 0]>, None::<u8>).await?
|
||||
}
|
||||
|
||||
async fn create_records(&self, payloads: Vec<RecordPayload>) -> Result<Vec<Record>, ()> {
|
||||
let result: RecordsResult = self.api_call("/records/bulk", Method::POST, None::<[(&str, &str); 0]>, Some(payloads)).await.map_err(|_| ())?;
|
||||
Ok(result.records)
|
||||
}
|
||||
|
||||
async fn update_records(&self, payloads: Vec<RecordPayload>) -> Result<Vec<Record>, ()> {
|
||||
let result: RecordsResult = self.api_call("/records/bulk", Method::PUT, None::<[(&str, &str); 0]>, Some(payloads)).await.map_err(|_| ())?;
|
||||
Ok(result.records)
|
||||
}
|
||||
}
|
13
src/main.rs
13
src/main.rs
|
@ -1,3 +1,12 @@
|
|||
fn main() {
|
||||
println!("Hello, world!");
|
||||
#![allow(dead_code, unused)]
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::models::*;
|
||||
use std::error::Error;
|
||||
|
||||
mod models;
|
||||
mod client;
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn main() -> () {
|
||||
}
|
||||
|
|
80
src/models.rs
Normal file
80
src/models.rs
Normal file
|
@ -0,0 +1,80 @@
|
|||
use serde::{Serialize, Deserialize};
|
||||
use chrono::{DateTime, Utc};
|
||||
use crate::client::RecordType;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct TxtVerification {
|
||||
pub name: String,
|
||||
pub token: String
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Pagination {
|
||||
pub page: u32,
|
||||
pub per_page: u32,
|
||||
pub last_page: u32,
|
||||
pub total_entries: u32
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Meta {
|
||||
pub pagination: Pagination
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Zone {
|
||||
pub id: String,
|
||||
pub created: DateTime<Utc>,
|
||||
pub modified: DateTime<Utc>,
|
||||
pub legacy_dns_host: String,
|
||||
pub legacy_dns: Vec<String>,
|
||||
pub ns: Vec<String>,
|
||||
pub owner: String,
|
||||
pub paused: bool,
|
||||
pub permission: String,
|
||||
pub project: String,
|
||||
pub registrar: String,
|
||||
pub status: String,
|
||||
pub ttl: u32,
|
||||
pub verified: DateTime<Utc>,
|
||||
pub records_count: u32,
|
||||
pub is_secondary_dns: bool,
|
||||
pub txt_verification: Vec<TxtVerification>
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct ZoneResult {
|
||||
pub zones: Vec<Zone>,
|
||||
pub meta: Meta
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct RecordPayload {
|
||||
zone_id: String,
|
||||
r#type: RecordType,
|
||||
name: String,
|
||||
value: String,
|
||||
ttl: u64
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Record {
|
||||
id: String,
|
||||
created: DateTime<Utc>,
|
||||
modified: DateTime<Utc>,
|
||||
zone_id: String,
|
||||
r#type: String,
|
||||
name: String,
|
||||
value: String,
|
||||
ttl: u64
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct RecordResult {
|
||||
pub record: Record
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct RecordsResult {
|
||||
pub records: Vec<Record>
|
||||
}
|
Loading…
Add table
Reference in a new issue