From 5755aedc2fb6a25bfdf0409a18d17cf22b240edb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kaan=20Barmore-Gen=C3=A7?= Date: Sun, 13 Nov 2022 15:36:21 -0500 Subject: [PATCH] Add CLI option to skip ipv4 or ipv6 (#58) closes #7 --- src/config.rs | 119 ++++++++++++++++++++++++++++++++++++++++++++++---- src/main.rs | 1 + src/opts.rs | 12 ++++- 3 files changed, 122 insertions(+), 10 deletions(-) diff --git a/src/config.rs b/src/config.rs index f8aaec5..c5694f4 100644 --- a/src/config.rs +++ b/src/config.rs @@ -5,10 +5,15 @@ use serde::Deserialize; use std::fs; use std::path::PathBuf; +fn default_types() -> Vec { + DEFAULT_TYPES.iter().map(|v| v.to_string()).collect() +} + #[derive(Deserialize, Debug)] pub struct Entry { pub name: String, - types: Option>, + #[serde(default = "default_types")] + types: Vec, fqdn: Option, ttl: Option, } @@ -54,11 +59,7 @@ impl Config { } pub fn types<'e>(entry: &'e Entry) -> Vec<&'e str> { - entry - .types - .as_ref() - .and_then(|ts| Some(ts.iter().map(|t| t.as_str()).collect())) - .unwrap_or_else(|| DEFAULT_TYPES.to_vec()) + entry.types.iter().map(|t| t.as_str()).collect() } } @@ -68,7 +69,7 @@ fn load_config_from>(path: P) -> anyhow: } pub fn load_config(opts: &opts::Opts) -> anyhow::Result { - match &opts.config { + let mut config = match &opts.config { Some(config_path) => load_config_from(&config_path), None => { let confpath = ProjectDirs::from("me", "kaangenc", "gandi-dynamic-dns") @@ -85,7 +86,23 @@ pub fn load_config(opts: &opts::Opts) -> anyhow::Result { load_config_from(path) }) } + }?; + // Filter out any types skipped in CLI opts + if opts.skip_ipv4 || opts.skip_ipv6 { + config.entry = config + .entry + .into_iter() + .map(|mut entry| { + entry.types = entry + .types + .into_iter() + .filter(|v| (v == "A" && !opts.skip_ipv4) || (v == "AAAA" && !opts.skip_ipv6)) + .collect(); + entry + }) + .collect(); } + Ok(config) } pub fn validate_config(config: &Config) -> anyhow::Result<()> { @@ -117,6 +134,9 @@ fqdn = "example.com" api_key = "xxx" ttl = 300 +[[entry]] +name = "www" + [[entry]] name = "@" "#, @@ -125,14 +145,18 @@ name = "@" let opts = Opts { config: Some(temp.to_string_lossy().to_string()), + ..Opts::default() }; let conf = load_config(&opts).expect("Failed to load config file"); assert_eq!(conf.fqdn, "example.com"); assert_eq!(conf.api_key, "xxx"); assert_eq!(conf.ttl, 300); - assert_eq!(conf.entry.len(), 1); - assert_eq!(conf.entry[0].name, "@"); + assert_eq!(conf.entry.len(), 2); + assert_eq!(conf.entry[0].name, "www"); + assert_eq!(conf.entry[0].types, vec!["A".to_string()]); + assert_eq!(conf.entry[1].name, "@"); + assert_eq!(conf.entry[1].types, vec!["A".to_string()]); // default assert_eq!(conf.ip_source, IPSourceName::Ipify); } @@ -161,6 +185,7 @@ name = "@" let opts = Opts { config: Some(temp.to_string_lossy().to_string()), + ..Opts::default() }; let conf = load_config(&opts).expect("Failed to load config file"); @@ -172,4 +197,80 @@ name = "@" assert_eq!(conf.entry[1].name, "@"); assert_eq!(conf.ip_source, IPSourceName::Icanhazip); } + + #[test] + fn load_config_skip_ipv4_with_opts() { + let mut temp = temp_dir().join("gandi-live-dns-test"); + fs::create_dir_all(&temp).expect("Failed to create test dir"); + temp.push("test-3.toml"); + fs::write( + &temp, + r#" +fqdn = "example.com" +api_key = "yyy" + +[[entry]] +name = "www" +types = ["A", "AAAA"] + +[[entry]] +name = "@" +types = ["A", "AAAA"] +"#, + ) + .expect("Failed to write test config file"); + + let opts = Opts { + config: Some(temp.to_string_lossy().to_string()), + skip_ipv4: true, + ..Opts::default() + }; + let conf = load_config(&opts).expect("Failed to load config file"); + + assert_eq!(conf.fqdn, "example.com"); + assert_eq!(conf.api_key, "yyy"); + assert_eq!(conf.entry.len(), 2); + assert_eq!(conf.entry[0].name, "www"); + assert_eq!(conf.entry[0].types, vec!["AAAA".to_string()]); + assert_eq!(conf.entry[1].name, "@"); + assert_eq!(conf.entry[1].types, vec!["AAAA".to_string()]); + } + + #[test] + fn load_config_skip_ipv6_with_opts() { + let mut temp = temp_dir().join("gandi-live-dns-test"); + fs::create_dir_all(&temp).expect("Failed to create test dir"); + temp.push("test-4.toml"); + fs::write( + &temp, + r#" +fqdn = "example.com" +api_key = "yyy" + +[[entry]] +name = "www" +types = ["A", "AAAA"] + +[[entry]] +name = "@" +types = ["A", "AAAA"] +"#, + ) + .expect("Failed to write test config file"); + + let opts = Opts { + config: Some(temp.to_string_lossy().to_string()), + skip_ipv6: true, + ..Opts::default() + }; + let conf = load_config(&opts).expect("Failed to load config file"); + + assert_eq!(conf.fqdn, "example.com"); + assert_eq!(conf.api_key, "yyy"); + assert_eq!(conf.entry.len(), 2); + assert_eq!(conf.entry[0].name, "www"); + assert_eq!(conf.entry[0].types, vec!["A".to_string()]); + assert_eq!(conf.entry[1].name, "@"); + assert_eq!(conf.entry[1].types, vec!["A".to_string()]); + } } diff --git a/src/main.rs b/src/main.rs index 89afc86..f491be2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -179,6 +179,7 @@ mod tests { let opts = Opts { config: Some(temp.to_string_lossy().to_string()), + ..Opts::default() }; let conf = config::load_config(&opts).expect("Failed to load config"); run::(server.base_url().as_str(), conf) diff --git a/src/opts.rs b/src/opts.rs index 65c6808..9eb5e4d 100644 --- a/src/opts.rs +++ b/src/opts.rs @@ -1,10 +1,20 @@ use clap::Parser; /// A tool to automatically update DNS entries on Gandi, using it as a dynamic DNS system. -#[derive(Parser, Debug)] +#[derive(Parser, Debug, Default)] #[clap(author, version, about, long_about = None, name = "gandi-live-dns")] pub struct Opts { /// The path to the configuration file. #[clap(long)] pub config: Option, + /// Skip IPv4 updates. + /// + /// If enabled, any IPv4 (A) records in the configuration file are ignored. + #[clap(action, long)] + pub skip_ipv4: bool, + /// Skip IPv4 updates. + /// + /// If enabled, any IPv6 (AAAA) records in the configuration file are ignored. + #[clap(action, long)] + pub skip_ipv6: bool, }