add multi name support and help texts
Some checks failed
Build legacy Nix package on Ubuntu / build (push) Failing after 2m4s

This commit is contained in:
bread 2025-04-22 03:06:26 +02:00
parent 824218ad73
commit 948da79c12
4 changed files with 106 additions and 30 deletions

View file

@ -8,4 +8,4 @@ chrono = { version = "0.4.40", features = ["serde"] }
reqwest = { version = "0.12.15", features = ["json"] } reqwest = { version = "0.12.15", features = ["json"] }
serde = { version = "1.0.219", features = ["derive"] } serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.140" serde_json = "1.0.140"
tokio = { version = "1.44.2", features = ["macros", "time"] } tokio = { version = "1.44.2", features = ["macros"] }

View file

@ -10,6 +10,7 @@
let let
pkgs = import nixpkgs { inherit system; }; pkgs = import nixpkgs { inherit system; };
naersk-lib = pkgs.callPackage naersk { }; naersk-lib = pkgs.callPackage naersk { };
package_name = "hetzner_dns";
in in
{ {
defaultPackage = naersk-lib.buildPackage ./.; defaultPackage = naersk-lib.buildPackage ./.;

View file

@ -1,11 +1,9 @@
use crate::*; use crate::*;
use reqwest::{ use reqwest::{
Client, Method, Request, Url, Client, Method, Request, Url,
header::{HeaderMap, HeaderValue}, header::HeaderValue,
}; };
use serde::{Serializer, de::Visitor}; use std::borrow::Borrow;
use serde_json::Value;
use std::{borrow::Borrow, collections::HashMap};
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub enum RecordType { pub enum RecordType {
@ -108,7 +106,6 @@ impl HetznerDNSAPIClient {
method, method,
self.host.join(url).map_err(|e| { self.host.join(url).map_err(|e| {
println!("url formatting error: {}", e); println!("url formatting error: {}", e);
()
})?, })?,
); );
req.headers_mut().append( req.headers_mut().append(
@ -120,11 +117,9 @@ impl HetznerDNSAPIClient {
serde_json::to_string(&payload) serde_json::to_string(&payload)
.map_err(|e| { .map_err(|e| {
println!("body encoding error: {}", e); println!("body encoding error: {}", e);
()
})? })?
.into(), .into(),
); );
println!("{:#?}", serde_json::to_string(&payload));
} }
let t = self let t = self
.client .client
@ -132,23 +127,19 @@ impl HetznerDNSAPIClient {
.await .await
.map_err(|e| { .map_err(|e| {
println!("request execution error: {}", e); println!("request execution error: {}", e);
()
})? })?
.error_for_status() .error_for_status()
.map_err(|e| { .map_err(|e| {
println!("request error: {}", e); println!("request error: {}", e);
()
})? })?
.text() .text()
.await .await
.map_err(|e| { .map_err(|e| {
println!("request decoding error: {}", e); println!("request decoding error: {}", e);
()
})?; })?;
Ok(serde_json::from_str::<T>(&t).map_err(|e| { serde_json::from_str::<T>(&t).map_err(|e| {
println!("json response parsing error: {}", e); println!("json response parsing error: {}", e);
() })
})?)
} }
pub async fn get_zones<'a>( pub async fn get_zones<'a>(

View file

@ -1,9 +1,7 @@
#![allow(dead_code, unused)]
use crate::client::*; use crate::client::*;
use crate::models::*; use crate::models::*;
use core::panic; use core::panic;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::ops::Sub;
mod client; mod client;
mod models; mod models;
@ -268,7 +266,7 @@ async fn main() {
.into_iter() .into_iter()
.next() .next()
.unwrap(); .unwrap();
let mut records = let records =
client.get_records(None, None, Some(zone.id)).await.unwrap(); client.get_records(None, None, Some(zone.id)).await.unwrap();
println!("{:#?}", records); println!("{:#?}", records);
} }
@ -287,9 +285,26 @@ async fn main() {
.unwrap()[0] .unwrap()[0]
.id; .id;
ctx.record_context.records[0].zone_id = zone.to_string(); ctx.record_context.records[0].zone_id = zone.to_string();
println!("{:#?}", ctx.record_context.records); if let Ok(record) = client
if let Ok(record) = .create_records(
client.create_records(ctx.record_context.records).await ctx.record_context
.records
.into_iter()
.flat_map(|r| {
r.name
.split(",")
.map(|s| RecordPayload {
zone_id: r.zone_id.clone(),
r#type: r.r#type.clone(),
name: String::from(s),
value: r.value.clone(),
ttl: r.ttl,
})
.collect::<Vec<_>>()
})
.collect::<Vec<_>>(),
)
.await
{ {
println!("{:#?}", record); println!("{:#?}", record);
} }
@ -317,10 +332,13 @@ async fn main() {
); );
} }
for old_record in records { for old_record in records {
if let Some(mut new_record) = if let Some(new_record) = records_iter.find(|r| {
records_iter.find(|r| r.name == old_record.name) r.name == old_record.name
{ || r.name
let mut old_record = old_record; .split(",")
.find(|s| *s == old_record.name)
.is_some()
}) {
updated_records.push(( updated_records.push((
old_record.id, old_record.id,
RecordPayload { RecordPayload {
@ -333,7 +351,7 @@ async fn main() {
)); ));
} }
} }
if updated_records.len() > 0 { if !updated_records.is_empty() {
let records = client.update_records(updated_records).await.unwrap(); let records = client.update_records(updated_records).await.unwrap();
eprintln!("Updated {} records", records.len()); eprintln!("Updated {} records", records.len());
} else { } else {
@ -354,14 +372,80 @@ async fn main() {
_ => panic!("how in the even"), _ => panic!("how in the even"),
} }
} else { } else {
panic!("missing token!"); panic!("missing token! set HETZNER_DNS_API_TOKEN or pass --token <token>");
} }
} else { } else {
match ctx.mode { match ctx.mode {
Mode::Zone => println!("zone help"), Mode::Zone => {
Mode::Record => println!("record help"), print_general_help();
Mode::PrimaryServer => println!("primary server help"), print_zone_help();
_ => println!("full help"), }
Mode::Record => {
print_general_help();
print_zone_help();
}
Mode::PrimaryServer => {
print_general_help();
print_primary_help();
}
_ => {
print_general_help();
print_zone_help();
print_record_help();
print_primary_help();
}
} }
} }
} }
fn print_general_help() {
println!(
"
Usage: hetzner_dns <subject> <action> <options>
Not passing an option will yield this help page.
Subjects:
- z|zones: manage zones/domains
- r|records: manage records for a zone
- p|primary: manage primary servers for a zone
Actions (valid for all subjects):
- g|get
- c|create
- u|update
- d|delete"
);
}
fn print_zone_help() {
println!(
"
Zone options per action:
- g|get [--all] [--zone <zone>]
- c|create [--name <name>] [--ttl <ttl>]
- u|update [--zone <zone>] [--name <name>] [--ttl <ttl>]
- d|delete [--zone <zone>]"
);
}
fn print_record_help() {
println!(
"
Record options per action:
- g|get [--all] [--zone <zone>]
- c|create [--zone <zone>] [--name <name>] [--value <value>] [--ttl <ttl>]
- u|update [--zone <zone>] [--name <name>] [--value <value>] [--ttl <ttl>]
- d|delete [--zone <zone>] [--name <name>]
Create will update existing records and update will create them if nescessary.
--name supports comma separated values to create/update multiple records with same values. (wip)
--ttl is always otpional and defaults to 86400."
);
}
fn print_primary_help() {
println!(
"
Missing implemention for primary servers - consider opening an issue at
https://git.thebread.dev/thebreadcompany/hetzner_dns/issues
if this feature is relevant for you!"
);
}