diff --git a/Cargo.lock b/Cargo.lock index 2ae70ae..a0a1669 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -382,7 +382,6 @@ dependencies = [ "chrono", "chrono-tz", "clap", - "envy", "futures", "http-auth", "http-link", @@ -446,15 +445,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" -[[package]] -name = "envy" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f47e0157f2cb54f5ae1bd371b30a2ae4311e1c028f575cd4e81de7353215965" -dependencies = [ - "serde", -] - [[package]] name = "equivalent" version = "1.0.2" diff --git a/Cargo.toml b/Cargo.toml index b132527..3fd3a6a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,6 @@ itertools = "0.14.0" serde_json = "1.0.133" serde = "1.0.215" tokio-cron-scheduler = { version = "0.13.0", default-features = false, optional = true } -envy = "0.4.2" chrono-tz = "0.10.3" [features] diff --git a/docs/src/content/docs/configuration/index.mdx b/docs/src/content/docs/configuration/index.mdx index 5bda255..a57afa2 100644 --- a/docs/src/content/docs/configuration/index.mdx +++ b/docs/src/content/docs/configuration/index.mdx @@ -121,6 +121,8 @@ Here are the ones currently available: - `CUP_REFRESH_INTERVAL` - Automatic refresh - `CUP_SOCKET` - Socket - `CUP_THEME` - Theme +- `CUP_IMAGES_EXCLUDE` - Images to exclude from checks, space-separated. +- `CUP_IMAGES_EXTRA` - Extra images to check, space-separated. Refer to the configuration page for more information on each of these. @@ -143,4 +145,4 @@ services: Heads up! Any configuration option you set with environment variables **always** overrides anything in your `cup.json`. - \ No newline at end of file + diff --git a/src/config.rs b/src/config.rs index a60aa44..9427967 100644 --- a/src/config.rs +++ b/src/config.rs @@ -2,19 +2,11 @@ use rustc_hash::FxHashMap; use serde::Deserialize; use serde::Deserializer; use std::env; -use std::mem; use std::path::PathBuf; use crate::error; -// We can't assign `a` to `b` in the loop in `Config::load`, so we'll have to use swap. It looks ugly so now we have a macro for it. -macro_rules! swap { - ($a:expr, $b:expr) => { - mem::swap(&mut $a, &mut $b) - }; -} - -#[derive(Clone, Deserialize)] +#[derive(Clone, Deserialize, Debug)] #[serde(rename_all = "snake_case")] pub enum Theme { Default, @@ -27,7 +19,18 @@ impl Default for Theme { } } -#[derive(Clone, Deserialize)] +impl std::str::FromStr for Theme { + type Err = String; + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "default" => Ok(Self::Default), + "blue" => Ok(Self::Blue), + _ => Err(format!("Invalid theme: {}", s)), + } + } +} + +#[derive(Clone, Deserialize, Debug)] #[serde(rename_all = "snake_case")] pub enum UpdateType { None, @@ -42,7 +45,20 @@ impl Default for UpdateType { } } -#[derive(Clone, Deserialize, Default)] +impl std::str::FromStr for UpdateType { + type Err = String; + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "none" => Ok(Self::None), + "major" => Ok(Self::Major), + "minor" => Ok(Self::Minor), + "patch" => Ok(Self::Patch), + _ => Err(format!("Invalid update type: {}", s)), + } + } +} + +#[derive(Clone, Deserialize, Default, Debug)] #[serde(deny_unknown_fields)] #[serde(default)] pub struct RegistryConfig { @@ -51,14 +67,14 @@ pub struct RegistryConfig { pub ignore: bool, } -#[derive(Clone, Deserialize, Default)] +#[derive(Clone, Deserialize, Default, Debug)] #[serde(default)] pub struct ImageConfig { pub extra: Vec, pub exclude: Vec, } -#[derive(Clone, Deserialize)] +#[derive(Clone, Deserialize, Debug)] #[serde(default)] pub struct Config { version: u8, @@ -89,40 +105,12 @@ impl Config { } /// Loads file and env config and merges them - pub fn load(&mut self, path: Option) -> Self { - let mut config = self.load_file(path); - - // Get environment variables with CUP_ prefix - let env_vars: FxHashMap = - env::vars().filter(|(k, _)| k.starts_with("CUP_")).collect(); - - if !env_vars.is_empty() { - if let Ok(mut cfg) = envy::prefixed("CUP_").from_env::() { - // If we have environment variables, override config.json options - for (key, _) in env_vars { - match key.as_str() { - "CUP_AGENT" => config.agent = cfg.agent, - #[rustfmt::skip] - "CUP_IGNORE_UPDATE_TYPE" => swap!(config.ignore_update_type, cfg.ignore_update_type), - #[rustfmt::skip] - "CUP_REFRESH_INTERVAL" => swap!(config.refresh_interval, cfg.refresh_interval), - "CUP_SOCKET" => swap!(config.socket, cfg.socket), - "CUP_THEME" => swap!(config.theme, cfg.theme), - // The syntax for these is slightly more complicated, not sure if they should be enabled or not. Let's stick to simple types for now. - // "CUP_IMAGES" => swap!(config.images, cfg.images), - // "CUP_REGISTRIES" => swap!(config.registries, cfg.registries), - // "CUP_SERVERS" => swap!(config.servers, cfg.servers), - _ => (), // Maybe print a warning if other CUP_ variables are detected - } - } - } - } - - config + pub fn load(path: Option) -> Self { + Config::load_file(path).load_env() } /// Reads the config from the file path provided and returns the parsed result. - fn load_file(&self, path: Option) -> Self { + fn load_file(path: Option) -> Self { let raw_config = match &path { Some(path) => std::fs::read_to_string(path), None => return Self::new(), // Empty config @@ -133,10 +121,10 @@ impl Config { &path.unwrap().to_str().unwrap() ) }; - self.parse(&raw_config.unwrap()) // We can safely unwrap here + Config::parse(&raw_config.unwrap()) // We can safely unwrap here } /// Parses and validates the config. - fn parse(&self, raw_config: &str) -> Self { + fn parse(raw_config: &str) -> Self { let config: Self = match serde_json::from_str(raw_config) { Ok(config) => config, Err(e) => error!("Unexpected error occured while parsing config: {}", e), @@ -146,6 +134,37 @@ impl Config { } config } + + /// Read and parse environment variables into the config object and return it. + fn load_env(mut self) -> Self { + env::vars() + .filter(|(k, _)| k.starts_with("CUP_")) + .for_each(|(key, value)| match key.as_str() { + "CUP_AGENT" => self.agent = value.parse().unwrap(), + "CUP_IGNORE_UPDATE_TYPE" => self.ignore_update_type = value.parse().unwrap(), + "CUP_REFRESH_INTERVAL" => self.refresh_interval = Some(value), + "CUP_SOCKET" => self.socket = Some(value), + "CUP_THEME" => self.theme = value.parse().unwrap(), + "CUP_IMAGES_EXCLUDE" => { + self.images.exclude = value + .split(' ') + .map(|s| s.trim().to_string()) + .filter(|s| !s.is_empty()) + .collect() + } + "CUP_IMAGES_EXTRA" => { + self.images.extra = value + .split(' ') + .map(|s| s.trim().to_string()) + .filter(|s| !s.is_empty()) + .collect() + } + // "CUP_REGISTRIES" => ... + // "CUP_SERVERS" => ... + _ => println!("Warning: Skip unknown CUP_ variable '{}'.", key), + }); + self + } } impl Default for Config { diff --git a/src/main.rs b/src/main.rs index 5bc4dc6..a3c3cbc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -79,7 +79,7 @@ async fn main() { "" => None, path => Some(PathBuf::from(path)), }; - let mut config = Config::new().load(cfg_path); + let mut config = Config::load(cfg_path); if let Some(socket) = cli.socket { config.socket = Some(socket) } @@ -87,6 +87,7 @@ async fn main() { config, logger: Logger::new(cli.debug, false), }; + ctx.logger.debug(format!("Config: {:?}", &ctx.config)); match &cli.command { #[cfg(feature = "cli")] Some(Commands::Check {