Skip to content

Commit b429d6c

Browse files
committed
config: support excluding images in environment variable.
1 parent 5015312 commit b429d6c

5 files changed

Lines changed: 70 additions & 59 deletions

File tree

Cargo.lock

Lines changed: 0 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ itertools = "0.14.0"
2525
serde_json = "1.0.133"
2626
serde = "1.0.215"
2727
tokio-cron-scheduler = { version = "0.13.0", default-features = false, optional = true }
28-
envy = "0.4.2"
2928
chrono-tz = "0.10.3"
3029

3130
[features]

docs/src/content/docs/configuration/index.mdx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,8 @@ Here are the ones currently available:
121121
- `CUP_REFRESH_INTERVAL` - Automatic refresh
122122
- `CUP_SOCKET` - Socket
123123
- `CUP_THEME` - Theme
124+
- `CUP_IMAGES_EXCLUDE` - Images to exclude from checks, space-separated.
125+
- `CUP_IMAGES_EXTRA` - Extra images to check, space-separated.
124126

125127
Refer to the configuration page for more information on each of these.
126128

@@ -143,4 +145,4 @@ services:
143145
<Callout>
144146
Heads up!
145147
Any configuration option you set with environment variables **always** overrides anything in your `cup.json`.
146-
</Callout>
148+
</Callout>

src/config.rs

Lines changed: 65 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,11 @@ use rustc_hash::FxHashMap;
22
use serde::Deserialize;
33
use serde::Deserializer;
44
use std::env;
5-
use std::mem;
65
use std::path::PathBuf;
76

87
use crate::error;
98

10-
// 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.
11-
macro_rules! swap {
12-
($a:expr, $b:expr) => {
13-
mem::swap(&mut $a, &mut $b)
14-
};
15-
}
16-
17-
#[derive(Clone, Deserialize)]
9+
#[derive(Clone, Deserialize, Debug)]
1810
#[serde(rename_all = "snake_case")]
1911
pub enum Theme {
2012
Default,
@@ -27,7 +19,18 @@ impl Default for Theme {
2719
}
2820
}
2921

30-
#[derive(Clone, Deserialize)]
22+
impl std::str::FromStr for Theme {
23+
type Err = String;
24+
fn from_str(s: &str) -> Result<Self, Self::Err> {
25+
match s.to_lowercase().as_str() {
26+
"default" => Ok(Self::Default),
27+
"blue" => Ok(Self::Blue),
28+
_ => Err(format!("Invalid theme: {}", s)),
29+
}
30+
}
31+
}
32+
33+
#[derive(Clone, Deserialize, Debug)]
3134
#[serde(rename_all = "snake_case")]
3235
pub enum UpdateType {
3336
None,
@@ -42,7 +45,20 @@ impl Default for UpdateType {
4245
}
4346
}
4447

45-
#[derive(Clone, Deserialize, Default)]
48+
impl std::str::FromStr for UpdateType {
49+
type Err = String;
50+
fn from_str(s: &str) -> Result<Self, Self::Err> {
51+
match s.to_lowercase().as_str() {
52+
"none" => Ok(Self::None),
53+
"major" => Ok(Self::Major),
54+
"minor" => Ok(Self::Minor),
55+
"patch" => Ok(Self::Patch),
56+
_ => Err(format!("Invalid update type: {}", s)),
57+
}
58+
}
59+
}
60+
61+
#[derive(Clone, Deserialize, Default, Debug)]
4662
#[serde(deny_unknown_fields)]
4763
#[serde(default)]
4864
pub struct RegistryConfig {
@@ -51,14 +67,14 @@ pub struct RegistryConfig {
5167
pub ignore: bool,
5268
}
5369

54-
#[derive(Clone, Deserialize, Default)]
70+
#[derive(Clone, Deserialize, Default, Debug)]
5571
#[serde(default)]
5672
pub struct ImageConfig {
5773
pub extra: Vec<String>,
5874
pub exclude: Vec<String>,
5975
}
6076

61-
#[derive(Clone, Deserialize)]
77+
#[derive(Clone, Deserialize, Debug)]
6278
#[serde(default)]
6379
pub struct Config {
6480
version: u8,
@@ -89,40 +105,12 @@ impl Config {
89105
}
90106

91107
/// Loads file and env config and merges them
92-
pub fn load(&mut self, path: Option<PathBuf>) -> Self {
93-
let mut config = self.load_file(path);
94-
95-
// Get environment variables with CUP_ prefix
96-
let env_vars: FxHashMap<String, String> =
97-
env::vars().filter(|(k, _)| k.starts_with("CUP_")).collect();
98-
99-
if !env_vars.is_empty() {
100-
if let Ok(mut cfg) = envy::prefixed("CUP_").from_env::<Config>() {
101-
// If we have environment variables, override config.json options
102-
for (key, _) in env_vars {
103-
match key.as_str() {
104-
"CUP_AGENT" => config.agent = cfg.agent,
105-
#[rustfmt::skip]
106-
"CUP_IGNORE_UPDATE_TYPE" => swap!(config.ignore_update_type, cfg.ignore_update_type),
107-
#[rustfmt::skip]
108-
"CUP_REFRESH_INTERVAL" => swap!(config.refresh_interval, cfg.refresh_interval),
109-
"CUP_SOCKET" => swap!(config.socket, cfg.socket),
110-
"CUP_THEME" => swap!(config.theme, cfg.theme),
111-
// 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.
112-
// "CUP_IMAGES" => swap!(config.images, cfg.images),
113-
// "CUP_REGISTRIES" => swap!(config.registries, cfg.registries),
114-
// "CUP_SERVERS" => swap!(config.servers, cfg.servers),
115-
_ => (), // Maybe print a warning if other CUP_ variables are detected
116-
}
117-
}
118-
}
119-
}
120-
121-
config
108+
pub fn load(path: Option<PathBuf>) -> Self {
109+
Config::load_file(path).load_env()
122110
}
123111

124112
/// Reads the config from the file path provided and returns the parsed result.
125-
fn load_file(&self, path: Option<PathBuf>) -> Self {
113+
fn load_file(path: Option<PathBuf>) -> Self {
126114
let raw_config = match &path {
127115
Some(path) => std::fs::read_to_string(path),
128116
None => return Self::new(), // Empty config
@@ -133,10 +121,10 @@ impl Config {
133121
&path.unwrap().to_str().unwrap()
134122
)
135123
};
136-
self.parse(&raw_config.unwrap()) // We can safely unwrap here
124+
Config::parse(&raw_config.unwrap()) // We can safely unwrap here
137125
}
138126
/// Parses and validates the config.
139-
fn parse(&self, raw_config: &str) -> Self {
127+
fn parse(raw_config: &str) -> Self {
140128
let config: Self = match serde_json::from_str(raw_config) {
141129
Ok(config) => config,
142130
Err(e) => error!("Unexpected error occured while parsing config: {}", e),
@@ -146,6 +134,37 @@ impl Config {
146134
}
147135
config
148136
}
137+
138+
/// Read and parse environment variables into the config object and return it.
139+
fn load_env(mut self) -> Self {
140+
env::vars()
141+
.filter(|(k, _)| k.starts_with("CUP_"))
142+
.for_each(|(key, value)| match key.as_str() {
143+
"CUP_AGENT" => self.agent = value.parse().unwrap(),
144+
"CUP_IGNORE_UPDATE_TYPE" => self.ignore_update_type = value.parse().unwrap(),
145+
"CUP_REFRESH_INTERVAL" => self.refresh_interval = Some(value),
146+
"CUP_SOCKET" => self.socket = Some(value),
147+
"CUP_THEME" => self.theme = value.parse().unwrap(),
148+
"CUP_IMAGES_EXCLUDE" => {
149+
self.images.exclude = value
150+
.split(' ')
151+
.map(|s| s.trim().to_string())
152+
.filter(|s| !s.is_empty())
153+
.collect()
154+
}
155+
"CUP_IMAGES_EXTRA" => {
156+
self.images.extra = value
157+
.split(' ')
158+
.map(|s| s.trim().to_string())
159+
.filter(|s| !s.is_empty())
160+
.collect()
161+
}
162+
// "CUP_REGISTRIES" => ...
163+
// "CUP_SERVERS" => ...
164+
_ => println!("Warning: Skip unknown CUP_ variable '{}'.", key),
165+
});
166+
self
167+
}
149168
}
150169

151170
impl Default for Config {

src/main.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,14 +79,15 @@ async fn main() {
7979
"" => None,
8080
path => Some(PathBuf::from(path)),
8181
};
82-
let mut config = Config::new().load(cfg_path);
82+
let mut config = Config::load(cfg_path);
8383
if let Some(socket) = cli.socket {
8484
config.socket = Some(socket)
8585
}
8686
let mut ctx = Context {
8787
config,
8888
logger: Logger::new(cli.debug, false),
8989
};
90+
ctx.logger.debug(format!("Config: {:?}", &ctx.config));
9091
match &cli.command {
9192
#[cfg(feature = "cli")]
9293
Some(Commands::Check {

0 commit comments

Comments
 (0)