From c2608744598b5297bfbfd104998c912ce9d1afa0 Mon Sep 17 00:00:00 2001 From: Sergio <77530549+sergi0g@users.noreply.github.com> Date: Sat, 10 May 2025 13:00:04 +0300 Subject: [PATCH] feat: add support for configuring through environment variables --- Cargo.lock | 10 ++++++++++ Cargo.toml | 1 + src/config.rs | 49 +++++++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 56 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 08077ba..6a24bbc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -360,6 +360,7 @@ dependencies = [ "bollard", "chrono", "clap", + "envy", "futures", "http-auth", "http-link", @@ -423,6 +424,15 @@ 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 5b87a5f..db2a2fa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ 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" [features] default = ["server", "cli"] diff --git a/src/config.rs b/src/config.rs index 9084607..d3b7b65 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,10 +1,18 @@ -use std::path::PathBuf; - use rustc_hash::FxHashMap; use serde::Deserialize; +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)] #[serde(rename_all = "snake_case")] pub enum Theme { @@ -78,8 +86,41 @@ 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 + } + /// Reads the config from the file path provided and returns the parsed result. - pub fn load(&self, path: Option) -> Self { + fn load_file(&self, path: Option) -> Self { let raw_config = match &path { Some(path) => std::fs::read_to_string(path), None => return Self::new(), // Empty config @@ -93,7 +134,7 @@ impl Config { self.parse(&raw_config.unwrap()) // We can safely unwrap here } /// Parses and validates the config. - pub fn parse(&self, raw_config: &str) -> Self { + fn parse(&self, 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),