From 4be26e5b424b01d0a2b2b35b978d839bc879e8a6 Mon Sep 17 00:00:00 2001 From: dotzenith Date: Fri, 30 Aug 2024 01:41:45 -0400 Subject: [PATCH] add a proper commandline interface --- Cargo.toml | 1 + src/common.rs | 9 +++++---- src/config_manager.rs | 8 ++++---- src/main.rs | 37 +++++++++++++++++++++++++++++++------ src/providers/cloudflare.rs | 10 +++++----- src/providers/namecheap.rs | 2 +- 6 files changed, 47 insertions(+), 20 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8abf3ef..aecf4b7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,3 +16,4 @@ serde_json = "1.0.127" config = { version = "0.14", default-features = false, features = ["yaml"] } simplelog = "0.12.2" log = "0.4.22" +clap = { version = "4.5.16", features = ["cargo"] } diff --git a/src/common.rs b/src/common.rs index f38648b..fbd1101 100644 --- a/src/common.rs +++ b/src/common.rs @@ -1,7 +1,7 @@ use anyhow::{Context, Result}; use reqwest::blocking::get; -use simplelog::{TermLogger, WriteLogger, LevelFilter, Config, TerminalMode, ColorChoice}; -use std::fs::File; +use simplelog::{ColorChoice, Config, LevelFilter, TermLogger, TerminalMode, WriteLogger}; +use std::fs::OpenOptions; use std::process; pub fn get_ip() -> Result { @@ -13,10 +13,11 @@ pub fn get_ip() -> Result { .to_owned()) } -pub fn init_logger(file: Option<&str>) { +pub fn init_logger(file: Option<&String>) { match file { Some(file_path) => { - let file = File::create(file_path); + let file = OpenOptions::new().create(true).append(true).open(file_path); + if file.is_err() { eprintln!("Unable to create log file"); process::exit(1); diff --git a/src/config_manager.rs b/src/config_manager.rs index 12eed75..c590a9e 100644 --- a/src/config_manager.rs +++ b/src/config_manager.rs @@ -3,8 +3,8 @@ use serde::Deserialize; #[derive(Debug, Deserialize)] pub struct CloudflareConfig { - pub api_key: String, - pub zone_name: String, + pub key: String, + pub zone: String, pub hostname: String, pub ttl: u32, pub proxied: bool, @@ -30,9 +30,9 @@ pub struct Settings { pub duckdns: Option>, } -pub fn config() -> Result { +pub fn config(path: &str) -> Result { let settings = Config::builder() - .add_source(File::new("example.yaml", FileFormat::Yaml)) + .add_source(File::new(path, FileFormat::Yaml)) .build()?; let settings: Settings = settings.try_deserialize()?; diff --git a/src/main.rs b/src/main.rs index 6299c9e..c80286c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,20 +3,45 @@ mod config_manager; mod providers; use crate::providers::{CloudflareManager, DuckdnsManager, NamecheapManager}; -use reqwest::blocking::Client; +use clap::{arg, command}; use common::init_logger; use log::{error, info}; +use reqwest::blocking::Client; fn main() { - let settings = config_manager::config().unwrap(); + let matches = command!() + .arg_required_else_help(true) + .arg( + arg!(-c --config ) + .required(true) + .help("The yaml config file to use"), + ) + .arg( + arg!(-l --log ) + .required(false) + .help("Where the output will be logged, uses stdout if not used"), + ) + .get_matches(); + + // Required so unwrap is swell + let config: &String = matches.get_one("config").unwrap(); + let log: Option<&String> = matches.get_one("log"); + + let settings = match config_manager::config(config) { + Ok(set) => set, + Err(err) => { + eprintln!("Config Error: {:?}", err); + std::process::exit(1); + } + }; let client = Client::new(); - init_logger(None); + init_logger(log); // Will exit if it doesn't succeed if let Some(cloudflare) = settings.cloudflare { for config in cloudflare.iter() { match CloudflareManager::new(&client).update_dns_record(config) { Ok(ok) => info!("Cloudflare: {}", ok), - Err(err) => error!("Cloudflare: {}", err) + Err(err) => error!("Cloudflare: {}", err), } } } @@ -25,7 +50,7 @@ fn main() { for config in namecheap.iter() { match NamecheapManager::new(&client).update_dns_record(config) { Ok(ok) => info!("Namecheap: {}", ok), - Err(err) => error!("Namecheap: {}", err) + Err(err) => error!("Namecheap: {}", err), } } } @@ -34,7 +59,7 @@ fn main() { for config in duckdns.iter() { match DuckdnsManager::new(&client).update_dns_record(config) { Ok(ok) => info!("Duckdns: {}", ok), - Err(err) => error!("Duckdns: {}", err) + Err(err) => error!("Duckdns: {}", err), } } } diff --git a/src/providers/cloudflare.rs b/src/providers/cloudflare.rs index 79898f1..7299a29 100644 --- a/src/providers/cloudflare.rs +++ b/src/providers/cloudflare.rs @@ -73,9 +73,9 @@ impl<'a> CloudflareManager<'a> { Err(anyhow!("Could not find record id")) } pub fn update_dns_record(&self, config: &CloudflareConfig) -> Result { - let zone_id = self.get_zone_id(&config.api_key, &config.zone_name)?; + let zone_id = self.get_zone_id(&config.key, &config.zone)?; let (record_id, current_ip) = - self.get_dns_record_id_and_ip(&zone_id, &config.hostname, &config.api_key)?; + self.get_dns_record_id_and_ip(&zone_id, &config.hostname, &config.key)?; let ip = get_ip()?; if current_ip == ip { @@ -93,7 +93,7 @@ impl<'a> CloudflareManager<'a> { .client .patch(&url) .header("Content-Type", "application/json") - .header("Authorization", format!("Bearer {}", &config.api_key)) + .header("Authorization", format!("Bearer {}", &config.key)) .json(&json!({ "content": ip, "name": &config.hostname, @@ -110,8 +110,8 @@ impl<'a> CloudflareManager<'a> { if success { return Ok(format!( - "Success! {} has been set to {}", - &config.hostname, ip + "Success! Hostname: {} for Zone: {} has been set to {}", + &config.hostname, &config.zone, ip )) } Err(anyhow!("Update failed: {}", json.to_string())) diff --git a/src/providers/namecheap.rs b/src/providers/namecheap.rs index a285314..5b21886 100644 --- a/src/providers/namecheap.rs +++ b/src/providers/namecheap.rs @@ -33,7 +33,7 @@ impl<'a> NamecheapManager<'a> { .context("Did not find any IP Addresses in response")?; if &captures[1] == ip { - Ok(format!("Success! {} has been set to {}", &config.host, ip)) + Ok(format!("Success! Host: {} for Domain: {} has been set to {}", &config.host, &config.domain, ip)) } else { Err(anyhow!("IP Address returned by the XML does not match")) }