mirror of
https://github.com/sergi0g/cup.git
synced 2025-11-15 08:33:49 -05:00
Change config, add schema
This commit is contained in:
61
cup.schema.json
Normal file
61
cup.schema.json
Normal file
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
22
src/check.rs
22
src/check.rs
@@ -63,7 +63,11 @@ pub async fn get_updates(references: &Option<Vec<String>>, config: &Config) -> V
|
|||||||
// Retrieve an authentication token (if required) for each registry.
|
// Retrieve an authentication token (if required) for each registry.
|
||||||
let mut tokens: FxHashMap<&str, Option<String>> = FxHashMap::default();
|
let mut tokens: FxHashMap<&str, Option<String>> = FxHashMap::default();
|
||||||
for registry in registries {
|
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 {
|
match check_auth(registry, config, &client).await {
|
||||||
Some(auth_url) => {
|
Some(auth_url) => {
|
||||||
let token = get_token(
|
let token = get_token(
|
||||||
@@ -85,12 +89,28 @@ pub async fn get_updates(references: &Option<Vec<String>>, config: &Config) -> V
|
|||||||
|
|
||||||
// Create a Vec to store futures so we can await them all at once.
|
// Create a Vec to store futures so we can await them all at once.
|
||||||
let mut handles = Vec::with_capacity(images.len());
|
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::<Vec<&String>>();
|
||||||
|
|
||||||
// Loop through images and get the latest digest for each
|
// Loop through images and get the latest digest for each
|
||||||
for image in &images {
|
for image in &images {
|
||||||
|
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 token = tokens.get(image.registry.as_str()).unwrap();
|
||||||
let future = image.check(token.as_ref(), config, &client);
|
let future = image.check(token.as_ref(), config, &client);
|
||||||
handles.push(future);
|
handles.push(future);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// Await all the futures
|
// Await all the futures
|
||||||
join_all(handles).await
|
join_all(handles).await
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,32 +19,44 @@ impl Default for Theme {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Deserialize)]
|
#[derive(Clone, Deserialize, Default)]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
|
#[serde(default)]
|
||||||
|
pub struct RegistryConfig {
|
||||||
|
pub authentication: Option<String>,
|
||||||
|
pub insecure: bool,
|
||||||
|
pub ignore: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Deserialize, Default)]
|
||||||
|
#[serde(default)]
|
||||||
|
pub struct ImageConfig {
|
||||||
|
pub extra: Vec<String>,
|
||||||
|
pub exclude: Vec<String>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Deserialize)]
|
||||||
|
#[serde(default)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
#[serde(default = "FxHashMap::default")]
|
pub registries: FxHashMap<String, RegistryConfig>,
|
||||||
pub authentication: FxHashMap<String, String>,
|
pub images: ImageConfig,
|
||||||
#[serde(default = "Theme::default")]
|
|
||||||
pub theme: Theme,
|
pub theme: Theme,
|
||||||
#[serde(default = "Vec::default")]
|
|
||||||
pub insecure_registries: Vec<String>,
|
|
||||||
pub socket: Option<String>,
|
pub socket: Option<String>,
|
||||||
#[serde(skip_deserializing)]
|
#[serde(skip_deserializing)]
|
||||||
pub debug: bool,
|
pub debug: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
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 {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
authentication: FxHashMap::default(),
|
registries: FxHashMap::default(),
|
||||||
|
images: ImageConfig::default(),
|
||||||
theme: Theme::Default,
|
theme: Theme::Default,
|
||||||
insecure_registries: Vec::with_capacity(0),
|
|
||||||
socket: None,
|
socket: None,
|
||||||
debug: false,
|
debug: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reads the config from the file path provided and returns the parsed result.
|
/// Reads the config from the file path provided and returns the parsed result.
|
||||||
pub fn load(&self, path: Option<PathBuf>) -> Self {
|
pub fn load(&self, path: Option<PathBuf>) -> Self {
|
||||||
let raw_config = match &path {
|
let raw_config = match &path {
|
||||||
@@ -67,3 +79,9 @@ impl Config {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for Config {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub async fn check_auth(registry: &str, config: &Config, client: &Client) -> Option<String> {
|
pub async fn check_auth(registry: &str, config: &Config, client: &Client) -> Option<String> {
|
||||||
let protocol = get_protocol(®istry.to_string(), &config.insecure_registries);
|
let protocol = get_protocol(registry, &config.registries);
|
||||||
let url = format!("{}://{}/v2/", protocol, registry);
|
let url = format!("{}://{}/v2/", protocol, registry);
|
||||||
let response = client.get(&url, Vec::new(), true).await;
|
let response = client.get(&url, Vec::new(), true).await;
|
||||||
match response {
|
match response {
|
||||||
@@ -51,7 +51,7 @@ pub async fn get_latest_digest(
|
|||||||
"Checking for digest update to {}", image.reference
|
"Checking for digest update to {}", image.reference
|
||||||
);
|
);
|
||||||
let start = timestamp();
|
let start = timestamp();
|
||||||
let protocol = get_protocol(&image.registry, &config.insecure_registries);
|
let protocol = get_protocol(&image.registry, &config.registries);
|
||||||
let url = format!(
|
let url = format!(
|
||||||
"{}://{}/v2/{}/manifests/{}",
|
"{}://{}/v2/{}/manifests/{}",
|
||||||
protocol, &image.registry, &image.repository, &image.tag
|
protocol, &image.registry, &image.repository, &image.tag
|
||||||
@@ -97,7 +97,7 @@ pub async fn get_latest_digest(
|
|||||||
pub async fn get_token(
|
pub async fn get_token(
|
||||||
images: &Vec<&Image>,
|
images: &Vec<&Image>,
|
||||||
auth_url: &str,
|
auth_url: &str,
|
||||||
credentials: &Option<&String>,
|
credentials: &Option<String>,
|
||||||
client: &Client,
|
client: &Client,
|
||||||
) -> String {
|
) -> String {
|
||||||
let mut url = auth_url.to_owned();
|
let mut url = auth_url.to_owned();
|
||||||
@@ -127,7 +127,7 @@ pub async fn get_latest_tag(
|
|||||||
"Checking for tag update to {}", image.reference
|
"Checking for tag update to {}", image.reference
|
||||||
);
|
);
|
||||||
let start = timestamp();
|
let start = timestamp();
|
||||||
let protocol = get_protocol(&image.registry, &config.insecure_registries);
|
let protocol = get_protocol(&image.registry, &config.registries);
|
||||||
let url = format!(
|
let url = format!(
|
||||||
"{}://{}/v2/{}/tags/list",
|
"{}://{}/v2/{}/tags/list",
|
||||||
protocol, &image.registry, &image.repository,
|
protocol, &image.registry, &image.repository,
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
use http_auth::parse_challenges;
|
use http_auth::parse_challenges;
|
||||||
use json::JsonValue;
|
use json::JsonValue;
|
||||||
use reqwest::Response;
|
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
|
/// Parses the www-authenticate header the registry sends into a challenge URL
|
||||||
pub fn parse_www_authenticate(www_auth: &str) -> String {
|
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 {
|
pub fn get_protocol(
|
||||||
if insecure_registries.contains(registry) {
|
registry: &str,
|
||||||
|
registry_config: &FxHashMap<String, RegistryConfig>,
|
||||||
|
) -> &'static str {
|
||||||
|
match registry_config.get(registry) {
|
||||||
|
Some(config) => {
|
||||||
|
if config.insecure {
|
||||||
"http"
|
"http"
|
||||||
} else {
|
} else {
|
||||||
"https"
|
"https"
|
||||||
}
|
}
|
||||||
.to_string()
|
},
|
||||||
|
None => "https"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_bearer_string(token: &Option<&String>) -> Option<String> {
|
pub fn to_bearer_string(token: &Option<&String>) -> Option<String> {
|
||||||
|
|||||||
Reference in New Issue
Block a user