diff --git a/README.md b/README.md index 36498f0..aa47fba 100644 --- a/README.md +++ b/README.md @@ -111,7 +111,7 @@ To see all available commands, simply run `managarr --help`: ```shell $ managarr --help -managarr 0.1.5 +managarr 0.2.1 Alex Clarke A TUI and CLI to manage your Servarrs @@ -199,45 +199,38 @@ managarr --config /path/to/config.yml ### Example Configuration: ```yaml radarr: - host: 127.0.0.1 + host: 192.168.0.78 port: 7878 api_token: someApiToken1234567890 - use_ssl: true - ssl_cert_path: /path/to/radarr.crt + ssl_cert_path: /path/to/radarr.crt # Required to enable SSL sonarr: - host: 127.0.0.1 - port: 8989 + uri: http://htpc.local/sonarr # Example of using the 'uri' key instead of 'host' and 'port' api_token: someApiToken1234567890 readarr: - host: 127.0.0.1 + host: 192.168.0.87 port: 8787 api_token: someApiToken1234567890 - use_ssl: false lidarr: - host: 127.0.0.1 + host: 192.168.0.86 port: 8686 api_token: someApiToken1234567890 - use_ssl: false whisparr: - host: 127.0.0.1 + host: 192.168.0.69 port: 6969 api_token: someApiToken1234567890 - use_ssl: false + ssl_cert_path: /path/to/whisparr.crt bazarr: - host: 127.0.0.1 + host: 192.168.0.67 port: 6767 api_token: someApiToken1234567890 - use_ssl: false prowlarr: - host: 127.0.0.1 + host: 192.168.0.96 port: 9696 api_token: someApiToken1234567890 - use_ssl: false tautulli: - host: 127.0.0.1 + host: 192.168.0.81 port: 8181 api_token: someApiToken1234567890 - use_ssl: false ``` ## Environment Variables diff --git a/src/app/app_tests.rs b/src/app/app_tests.rs index 3ecef14..e2b429f 100644 --- a/src/app/app_tests.rs +++ b/src/app/app_tests.rs @@ -1,7 +1,7 @@ #[cfg(test)] mod tests { use anyhow::anyhow; - use pretty_assertions::{assert_eq, assert_str_eq}; + use pretty_assertions::assert_eq; use tokio::sync::mpsc; use crate::app::context_clues::{build_context_clue_string, SERVARR_CONTEXT_CLUES}; @@ -221,10 +221,10 @@ mod tests { fn test_radarr_config_default() { let radarr_config = RadarrConfig::default(); - assert_str_eq!(radarr_config.host, "localhost"); + assert_eq!(radarr_config.host, Some("localhost".to_string())); assert_eq!(radarr_config.port, Some(7878)); + assert_eq!(radarr_config.uri, None); assert!(radarr_config.api_token.is_empty()); - assert!(!radarr_config.use_ssl); assert_eq!(radarr_config.ssl_cert_path, None); } } diff --git a/src/app/mod.rs b/src/app/mod.rs index 685258c..970877c 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -1,4 +1,7 @@ +use std::process; + use anyhow::anyhow; +use colored::Colorize; use log::{debug, error}; use serde::{Deserialize, Serialize}; use tokio::sync::mpsc::Sender; @@ -166,29 +169,52 @@ pub struct Data<'a> { pub radarr_data: RadarrData<'a>, } +pub trait ServarrConfig { + fn validate(&self); +} + #[derive(Debug, Deserialize, Serialize, Default)] pub struct AppConfig { pub radarr: RadarrConfig, } +impl ServarrConfig for AppConfig { + fn validate(&self) { + self.radarr.validate(); + } +} + #[derive(Debug, Deserialize, Serialize)] pub struct RadarrConfig { - pub host: String, + pub host: Option, pub port: Option, + pub uri: Option, pub api_token: String, - #[serde(default)] - pub use_ssl: bool, pub ssl_cert_path: Option, } +impl ServarrConfig for RadarrConfig { + fn validate(&self) { + if self.host.is_none() && self.uri.is_none() { + log_and_print_error("'host' or 'uri' is required for Radarr configuration".to_owned()); + process::exit(1); + } + } +} + impl Default for RadarrConfig { fn default() -> Self { RadarrConfig { - host: "localhost".to_string(), + host: Some("localhost".to_string()), port: Some(7878), + uri: None, api_token: "".to_string(), - use_ssl: false, ssl_cert_path: None, } } } + +pub fn log_and_print_error(error: String) { + error!("{}", error); + eprintln!("error: {}", error.red()); +} diff --git a/src/main.rs b/src/main.rs index b2922b3..36af21f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,7 +10,7 @@ use std::{io, panic, process}; use anyhow::anyhow; use anyhow::Result; -use app::AppConfig; +use app::{log_and_print_error, AppConfig, ServarrConfig}; use clap::{ command, crate_authors, crate_description, crate_name, crate_version, CommandFactory, Parser, }; @@ -88,6 +88,7 @@ async fn main() -> Result<()> { } else { confy::load("managarr", "config")? }; + config.validate(); let reqwest_client = build_network_client(&config); let (sync_network_tx, sync_network_rx) = mpsc::channel(500); let cancellation_token = CancellationToken::new(); @@ -229,8 +230,8 @@ fn load_config(path: &str) -> Result { fn build_network_client(config: &AppConfig) -> Client { let mut client_builder = Client::builder(); - if config.radarr.use_ssl { - let cert = create_cert(config.radarr.ssl_cert_path.clone(), "Radarr"); + if let Some(ref cert_path) = config.radarr.ssl_cert_path { + let cert = create_cert(cert_path, "Radarr"); client_builder = client_builder.add_root_certificate(cert); } @@ -244,32 +245,25 @@ fn build_network_client(config: &AppConfig) -> Client { } } -fn create_cert(cert_path: Option, servarr_name: &str) -> Certificate { - let err = |error: String| { - error!("{}", error); - eprintln!("error: {}", error.red()); - process::exit(1); - }; - - if cert_path.is_none() { - err(format!( - "A {} cert path is required when 'use_ssl' is 'true'", - servarr_name - )); - } - - match fs::read(cert_path.unwrap()) { +fn create_cert(cert_path: &String, servarr_name: &str) -> Certificate { + match fs::read(cert_path) { Ok(cert) => match Certificate::from_pem(&cert) { Ok(certificate) => certificate, - Err(_) => err(format!( - "Unable to read the specified {} SSL certificate", - servarr_name - )), + Err(_) => { + log_and_print_error(format!( + "Unable to read the specified {} SSL certificate", + servarr_name + )); + process::exit(1); + } }, - Err(_) => err(format!( - "Unable to open specified {} SSL certificate", - servarr_name - )), + Err(_) => { + log_and_print_error(format!( + "Unable to open specified {} SSL certificate", + servarr_name + )); + process::exit(1); + } } } diff --git a/src/network/network_tests.rs b/src/network/network_tests.rs index ffed03a..e26f8af 100644 --- a/src/network/network_tests.rs +++ b/src/network/network_tests.rs @@ -26,7 +26,7 @@ mod tests { .with_body("{}") .create_async() .await; - let host = server.host_with_port().split(':').collect::>()[0].to_owned(); + let host = Some(server.host_with_port().split(':').collect::>()[0].to_owned()); let port = Some( server.host_with_port().split(':').collect::>()[1] .parse() @@ -38,8 +38,8 @@ mod tests { host, api_token: String::new(), port, - use_ssl: false, ssl_cert_path: None, + ..RadarrConfig::default() }; app.config.radarr = radarr_config; let app_arc = Arc::new(Mutex::new(app)); diff --git a/src/network/radarr_network.rs b/src/network/radarr_network.rs index 4a632d5..078dae0 100644 --- a/src/network/radarr_network.rs +++ b/src/network/radarr_network.rs @@ -2261,15 +2261,24 @@ impl<'a, 'b> Network<'a, 'b> { let RadarrConfig { host, port, + uri, api_token, - use_ssl, - .. + ssl_cert_path, } = &app.config.radarr; - let protocol = if *use_ssl { "https" } else { "http" }; - let uri = format!( - "{protocol}://{host}:{}/api/v3{resource}", - port.unwrap_or(7878) - ); + let uri = if let Some(radarr_uri) = uri { + format!("{radarr_uri}/api/v3{resource}") + } else { + let protocol = if ssl_cert_path.is_some() { + "https" + } else { + "http" + }; + let host = host.as_ref().unwrap(); + format!( + "{protocol}://{host}:{}/api/v3{resource}", + port.unwrap_or(7878) + ) + }; RequestProps { uri, diff --git a/src/network/radarr_network_tests.rs b/src/network/radarr_network_tests.rs index 80f4097..67500b6 100644 --- a/src/network/radarr_network_tests.rs +++ b/src/network/radarr_network_tests.rs @@ -794,7 +794,7 @@ mod test { .match_header("X-Api-Key", "test1234"); async_server = async_server.expect_at_most(0).create_async().await; - let host = server.host_with_port().split(':').collect::>()[0].to_owned(); + let host = Some(server.host_with_port().split(':').collect::>()[0].to_owned()); let port = Some( server.host_with_port().split(':').collect::>()[1] .parse() @@ -805,8 +805,7 @@ mod test { host, port, api_token: "test1234".to_owned(), - use_ssl: false, - ssl_cert_path: None, + ..RadarrConfig::default() }; app.config.radarr = radarr_config; let app_arc = Arc::new(Mutex::new(app)); @@ -4876,11 +4875,10 @@ mod test { assert!(request_props.api_token.is_empty()); app_arc.lock().await.config.radarr = RadarrConfig { - host: "192.168.0.123".to_owned(), + host: Some("192.168.0.123".to_owned()), port: Some(8080), api_token: "testToken1234".to_owned(), - use_ssl: false, - ssl_cert_path: None, + ..RadarrConfig::default() }; } @@ -4889,11 +4887,33 @@ mod test { let api_token = "testToken1234".to_owned(); let app_arc = Arc::new(Mutex::new(App::default())); app_arc.lock().await.config.radarr = RadarrConfig { - host: "192.168.0.123".to_owned(), + host: Some("192.168.0.123".to_owned()), port: Some(8080), api_token: api_token.clone(), - use_ssl: true, - ssl_cert_path: None, + ssl_cert_path: Some("/test/cert.crt".to_owned()), + ..RadarrConfig::default() + }; + let network = Network::new(&app_arc, CancellationToken::new(), Client::new()); + + let request_props = network + .radarr_request_props_from("/test", RequestMethod::Get, None::<()>) + .await; + + assert_str_eq!(request_props.uri, "https://192.168.0.123:8080/api/v3/test"); + assert_eq!(request_props.method, RequestMethod::Get); + assert_eq!(request_props.body, None); + assert_str_eq!(request_props.api_token, api_token); + } + + #[tokio::test] + async fn test_radarr_request_props_from_custom_radarr_config_using_uri_instead_of_host_and_port() + { + let api_token = "testToken1234".to_owned(); + let app_arc = Arc::new(Mutex::new(App::default())); + app_arc.lock().await.config.radarr = RadarrConfig { + uri: Some("https://192.168.0.123:8080".to_owned()), + api_token: api_token.clone(), + ..RadarrConfig::default() }; let network = Network::new(&app_arc, CancellationToken::new(), Client::new()); @@ -4986,7 +5006,7 @@ mod test { async_server = async_server.create_async().await; - let host = server.host_with_port().split(':').collect::>()[0].to_owned(); + let host = Some(server.host_with_port().split(':').collect::>()[0].to_owned()); let port = Some( server.host_with_port().split(':').collect::>()[1] .parse() @@ -4997,8 +5017,7 @@ mod test { host, port, api_token: "test1234".to_owned(), - use_ssl: false, - ssl_cert_path: None, + ..RadarrConfig::default() }; app.config.radarr = radarr_config; let app_arc = Arc::new(Mutex::new(app));