From 33a72c8c0d130e8ccd839b3a24b4a1df6e385904 Mon Sep 17 00:00:00 2001 From: Sergio <77530549+sergi0g@users.noreply.github.com> Date: Sat, 7 Dec 2024 16:21:01 +0200 Subject: [PATCH] Change config, add schema --- cup.schema.json | 61 ++++++++++++++++++++++++++++++++++++++++++++ src/check.rs | 28 +++++++++++++++++--- src/config.rs | 38 +++++++++++++++++++-------- src/registry.rs | 8 +++--- src/utils/request.rs | 22 +++++++++++----- 5 files changed, 132 insertions(+), 25 deletions(-) create mode 100644 cup.schema.json diff --git a/cup.schema.json b/cup.schema.json new file mode 100644 index 0000000..aaed5be --- /dev/null +++ b/cup.schema.json @@ -0,0 +1,61 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://raw.githubusercontent.com/sergi0g/cup/main/cup.schema.json", + "title": "Cup", + "description": "A schema for Cup's config file", + "type": "object", + "properties": { + "registries": { + "type": "object", + "description": "Configuration options for specific registries", + "additionalProperties": { + "authentication": { + "description": "An authentication token provided by the registry", + "type": "string", + "minLength": 1 + }, + "insecure": { + "description": "Whether Cup should connect to the registry insecurely (HTTP) or not. Enable this only if you really need to.", + "type": "boolean" + }, + "ignore": { + "description": "Whether or not the registry should be ignored when running Cup", + "type": "boolean" + } + } + }, + "images": { + "type": "object", + "description": "Configuration options for specific images", + "properties": { + "extra": { + "type": "array", + "description": "Extra image references you want Cup to check", + "minItems": 1 + }, + "exclude": { + "type": "array", + "description": "Image references that should be excluded from the check", + "minItems": 1, + "items": { + "type": "string", + "minLength": 1 + } + } + } + }, + "theme": { + "description": "The theme used by the web UI", + "type": "string", + "enum": [ + "default", + "blue" + ] + }, + "socket": { + "description": "The path to the unix socket you would like Cup to use for communication with the Docker daemon. Useful if you're trying to use Cup with Podman.", + "type": "string", + "minLength": 1 + } + } +} \ No newline at end of file diff --git a/src/check.rs b/src/check.rs index 32e8a65..6aefcc0 100644 --- a/src/check.rs +++ b/src/check.rs @@ -63,7 +63,11 @@ pub async fn get_updates(references: &Option>, config: &Config) -> V // Retrieve an authentication token (if required) for each registry. let mut tokens: FxHashMap<&str, Option> = FxHashMap::default(); for registry in registries { - let credentials = config.authentication.get(registry); + let credentials = if let Some(registry_config) = config.registries.get(registry) { + ®istry_config.authentication + } else { + &None + }; match check_auth(registry, config, &client).await { Some(auth_url) => { let token = get_token( @@ -85,11 +89,27 @@ pub async fn get_updates(references: &Option>, config: &Config) -> V // Create a Vec to store futures so we can await them all at once. let mut handles = Vec::with_capacity(images.len()); + + let ignored_registries = config + .registries + .iter() + .filter_map(|(registry, registry_config)| { + if registry_config.ignore { + Some(registry) + } else { + None + } + }) + .collect::>(); + // Loop through images and get the latest digest for each for image in &images { - let token = tokens.get(image.registry.as_str()).unwrap(); - let future = image.check(token.as_ref(), config, &client); - handles.push(future); + let is_ignored = ignored_registries.contains(&&image.registry) || config.images.exclude.iter().any(|item| image.reference.starts_with(item)); + if !is_ignored { + let token = tokens.get(image.registry.as_str()).unwrap(); + let future = image.check(token.as_ref(), config, &client); + handles.push(future); + } } // Await all the futures join_all(handles).await diff --git a/src/config.rs b/src/config.rs index e3f56b4..610fbd2 100644 --- a/src/config.rs +++ b/src/config.rs @@ -19,32 +19,44 @@ impl Default for Theme { } } -#[derive(Clone, Deserialize)] +#[derive(Clone, Deserialize, Default)] #[serde(deny_unknown_fields)] +#[serde(default)] +pub struct RegistryConfig { + pub authentication: Option, + pub insecure: bool, + pub ignore: bool, +} + +#[derive(Clone, Deserialize, Default)] +#[serde(default)] +pub struct ImageConfig { + pub extra: Vec, + pub exclude: Vec +} + +#[derive(Clone, Deserialize)] +#[serde(default)] pub struct Config { - #[serde(default = "FxHashMap::default")] - pub authentication: FxHashMap, - #[serde(default = "Theme::default")] + pub registries: FxHashMap, + pub images: ImageConfig, pub theme: Theme, - #[serde(default = "Vec::default")] - pub insecure_registries: Vec, pub socket: Option, #[serde(skip_deserializing)] pub debug: bool, } impl Config { - /// A stupid new function that exists just so calling `load` doesn't require a self argument - #[allow(clippy::new_without_default)] pub fn new() -> Self { Self { - authentication: FxHashMap::default(), + registries: FxHashMap::default(), + images: ImageConfig::default(), theme: Theme::Default, - insecure_registries: Vec::with_capacity(0), socket: None, debug: false, } } + /// Reads the config from the file path provided and returns the parsed result. pub fn load(&self, path: Option) -> Self { let raw_config = match &path { @@ -67,3 +79,9 @@ impl Config { } } } + +impl Default for Config { + fn default() -> Self { + Self::new() + } +} diff --git a/src/registry.rs b/src/registry.rs index dd87b4d..54e5db8 100644 --- a/src/registry.rs +++ b/src/registry.rs @@ -18,7 +18,7 @@ use crate::{ }; pub async fn check_auth(registry: &str, config: &Config, client: &Client) -> Option { - let protocol = get_protocol(®istry.to_string(), &config.insecure_registries); + let protocol = get_protocol(registry, &config.registries); let url = format!("{}://{}/v2/", protocol, registry); let response = client.get(&url, Vec::new(), true).await; match response { @@ -51,7 +51,7 @@ pub async fn get_latest_digest( "Checking for digest update to {}", image.reference ); let start = timestamp(); - let protocol = get_protocol(&image.registry, &config.insecure_registries); + let protocol = get_protocol(&image.registry, &config.registries); let url = format!( "{}://{}/v2/{}/manifests/{}", protocol, &image.registry, &image.repository, &image.tag @@ -97,7 +97,7 @@ pub async fn get_latest_digest( pub async fn get_token( images: &Vec<&Image>, auth_url: &str, - credentials: &Option<&String>, + credentials: &Option, client: &Client, ) -> String { let mut url = auth_url.to_owned(); @@ -127,7 +127,7 @@ pub async fn get_latest_tag( "Checking for tag update to {}", image.reference ); let start = timestamp(); - let protocol = get_protocol(&image.registry, &config.insecure_registries); + let protocol = get_protocol(&image.registry, &config.registries); let url = format!( "{}://{}/v2/{}/tags/list", protocol, &image.registry, &image.repository, diff --git a/src/utils/request.rs b/src/utils/request.rs index efbbd7a..180c74b 100644 --- a/src/utils/request.rs +++ b/src/utils/request.rs @@ -1,8 +1,9 @@ use http_auth::parse_challenges; use json::JsonValue; use reqwest::Response; +use rustc_hash::FxHashMap; -use crate::error; +use crate::{config::RegistryConfig, error}; /// Parses the www-authenticate header the registry sends into a challenge URL pub fn parse_www_authenticate(www_auth: &str) -> String { @@ -23,13 +24,20 @@ pub fn parse_www_authenticate(www_auth: &str) -> String { } } -pub fn get_protocol(registry: &String, insecure_registries: &[String]) -> String { - if insecure_registries.contains(registry) { - "http" - } else { - "https" +pub fn get_protocol( + registry: &str, + registry_config: &FxHashMap, +) -> &'static str { + match registry_config.get(registry) { + Some(config) => { + if config.insecure { + "http" + } else { + "https" + } + }, + None => "https" } - .to_string() } pub fn to_bearer_string(token: &Option<&String>) -> Option {