From 093b99d69d7f60b67f34ec09badb04e3d0c3d943 Mon Sep 17 00:00:00 2001 From: Kaan Genc Date: Fri, 24 Dec 2021 20:20:31 -0500 Subject: [PATCH] almost done! --- .vscode/settings.json | 1 + Cargo.lock | 98 ++++++++++++++++++++++++++++++++++--------- Cargo.toml | 2 + example.toml | 26 +++++++++++- src/config.rs | 35 +++++++++++++++- src/main.rs | 42 ++++++++++++++++--- 6 files changed, 176 insertions(+), 28 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 7c86574..6321b1e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,7 @@ { "cSpell.words": [ "gandi", + "rrset", "structopt" ] } \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 91d0c45..caae4d4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -156,42 +156,92 @@ dependencies = [ ] [[package]] -name = "futures-channel" -version = "0.3.18" +name = "futures" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fc8cd39e3dbf865f7340dce6a2d401d24fd37c6fe6c4f0ee0de8bfca2252d27" +checksum = "28560757fe2bb34e79f907794bb6b22ae8b0e5c669b638a1132f2592b19035b4" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3dda0b6588335f360afc675d0564c17a77a2bda81ca178a4b6081bd86c7f0b" dependencies = [ "futures-core", + "futures-sink", ] [[package]] name = "futures-core" -version = "0.3.18" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "629316e42fe7c2a0b9a65b47d159ceaa5453ab14e8f0a3c5eedbb8cd55b4a445" +checksum = "d0c8ff0461b82559810cdccfde3215c3f373807f5e5232b71479bff7bb2583d7" [[package]] -name = "futures-sink" -version = "0.3.18" +name = "futures-executor" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "996c6442437b62d21a32cd9906f9c41e7dc1e19a9579843fad948696769305af" - -[[package]] -name = "futures-task" -version = "0.3.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dabf1872aaab32c886832f2276d2f5399887e2bd613698a02359e4ea83f8de12" - -[[package]] -name = "futures-util" -version = "0.3.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d22213122356472061ac0f1ab2cee28d2bac8491410fd68c2af53d1cedb83e" +checksum = "29d6d2ff5bb10fb95c85b8ce46538a2e5f5e7fdc755623a7d4529ab8a4ed9d2a" dependencies = [ "futures-core", "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f9d34af5a1aac6fb380f735fe510746c38067c5bf16c7fd250280503c971b2" + +[[package]] +name = "futures-macro" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbd947adfffb0efc70599b3ddcf7b5597bb5fa9e245eb99f62b3a5f7bb8bd3c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3055baccb68d74ff6480350f8d6eb8fcfa3aa11bdc1a1ae3afdd0514617d508" + +[[package]] +name = "futures-task" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ee7c6485c30167ce4dfb83ac568a849fe53274c831081476ee13e0dce1aad72" + +[[package]] +name = "futures-util" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b5cf40b47a271f77a8b1bec03ca09044d99d2372c0de244e66430761127164" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", "pin-project-lite", "pin-utils", + "slab", ] [[package]] @@ -199,6 +249,8 @@ name = "gandi-rust-dns-updater" version = "0.1.0" dependencies = [ "directories", + "futures", + "json", "reqwest", "serde", "structopt", @@ -382,6 +434,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "json" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" + [[package]] name = "lazy_static" version = "1.4.0" diff --git a/Cargo.toml b/Cargo.toml index d944b57..36de4c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,9 @@ edition = "2021" reqwest = { version = "0.11.7", features = ["json"] } toml = "0.5.8" +json = "0.12.4" serde = { version = "1.0", features = ["derive"] } directories = "4.0.1" structopt = "0.3.25" tokio = { version = "1.14.0", features = ["full"] } +futures = "0.3.17" \ No newline at end of file diff --git a/example.toml b/example.toml index 825073e..3d8a432 100644 --- a/example.toml +++ b/example.toml @@ -1,2 +1,26 @@ -fqdn = "my-website.example.com" +# Set the domain that you want to configure. +fqdn = "example.com" +# The API key to use. To get your API key, log in to Gandi, click on your user +# on the top right and click settings. Then select the "Security" tab, and +# generate an API key under "Production API Key". Paste the key here. +# +# Make sure this file config file is not readable by your user! Anyone who can +# read this key can change your domain configuration, transfer your domains, or +# otherwise do things that will cause you to be charged money. api_key = "xxxxxxxxxxxxxxxxxxxxxxxx" + +# For every domain or subdomain you want to update, create an entry below. + +[[entry]] +# Updates A (IPv4) entry for example.com +name = "@" + +[[entry]] +# Updates both A (IPv4) and AAA (IPv6) entries for other.example.com +name = "other" +types = ["A", "AAA"] + +[[entry]] +# Updates A for some.example.net +name = "some" +fqdn = "example.net" # Overrides top level setting diff --git a/src/config.rs b/src/config.rs index 0c83d89..5d09b32 100644 --- a/src/config.rs +++ b/src/config.rs @@ -5,10 +5,32 @@ use std::fs; use std::path::PathBuf; use crate::opts; +#[derive(Deserialize, Debug)] +pub struct Entry { + pub name: String, + types: Option>, + fqdn: Option, +} + #[derive(Deserialize, Debug)] pub struct Config { - pub fqdn: String, + fqdn: String, pub api_key: String, + pub entry: Vec, +} + +const DEFAULT_TYPES: Vec<&str> = vec!["A"]; + +impl Config { + pub fn fqdn<'c>(entry: &'c Entry, config: &'c Config) -> &'c str { + return entry.fqdn.as_ref().unwrap_or(&config.fqdn).as_str(); + } + + pub fn types<'e>(entry: &'e Entry) -> Vec<&'e str> { + return entry.types.as_ref().and_then( + |ts| Some(ts.iter().map(|t| t.as_str()).collect()) + ).unwrap_or(DEFAULT_TYPES); + } } pub fn load_config(file: PathBuf) -> Result> { @@ -19,6 +41,17 @@ pub fn load_config(file: PathBuf) -> Result> { return Ok(config); } +pub fn validate_config(config: &Config) -> Result<(), String> { + for entry in &config.entry { + for entry_type in Config::types(&entry) { + if entry_type != "A" && entry_type != "AAA" { + return Err(format!("Entry {} has invalid type {}", entry.name, entry_type)); + } + } + } + return Ok(()); +} + pub fn config_path(opts: &opts::Opts) -> PathBuf { return opts .config diff --git a/src/main.rs b/src/main.rs index 4a7f812..9b476e2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,14 +1,21 @@ +use std::collections::HashMap; +use crate::config::Config; use reqwest::{header, Client, ClientBuilder}; use std::error::Error; use structopt::StructOpt; use tokio; +use futures; mod config; mod opts; -fn gandi_api(fqdn: &str) -> String { +fn gandi_api_get(fqdn: &str) -> String { return format!("https://api.gandi.net/v5/livedns/domains/{}/records", fqdn); } +fn gandi_api_url(fqdn: &str, rrset_name: &str, rrset_type: &str) -> String { + return format!(" https://api.gandi.net/v5/livedns/domains/{}/records/{}/{}", fqdn, rrset_name, rrset_type); +} + fn api_client(api_key: &str) -> Result> { let client_builder = ClientBuilder::new(); @@ -29,13 +36,36 @@ async fn main() -> Result<(), Box> { let conf_path = config::config_path(&opts); println!("Loading config from {:#?}", conf_path); let conf = config::load_config(conf_path)?; - println!("Checking domain {:#?}", conf.fqdn); - let url = gandi_api(&conf.fqdn); + config::validate_config(&conf)?; + let client = api_client(&conf.api_key)?; - let out = client.get(url).send().await?; - println!("Output: {:#?}", out); - println!("Output: {:#?}", out.json().await?); + let ipv4 = String::from("173.89.215.91"); + let ipv6 = String::from("2603:6011:be07:302:79f4:50dd:6abe:be38"); + + let mut results = Vec::new(); + + for entry in &conf.entry { + for entry_type in Config::types(entry) { + let fqdn = Config::fqdn(&entry, &conf); + let url = gandi_api_url(fqdn, entry.name.as_str(), entry_type); + let ip = if entry_type.eq("A") { ipv4.as_str() } else { ipv6.as_str() }; + let mut map = HashMap::new(); + map.insert("rrset_values", ip); + let req = client.put(url).json(&map); + let task = tokio::task::spawn(async move { + let response = req.send().await?; + return (response.status(), response.text().await?); + }); + results.push(task); + } + } + + let results = futures::future::try_join_all(results).await?; + + for (status, body) in results { + println!("{} - {}", status, body); + } return Ok(()); }